
- 在APIEndpoints.swift中新增publishFeed端点以支持发布动态。 - 新增PublishFeedRequest和PublishFeedResponse模型,处理发布请求和响应。 - 在EditFeedFeature中实现动态编辑功能,支持用户输入和发布内容。 - 更新CreateFeedView和EditFeedView以集成新的发布功能,提升用户体验。 - 在Localizable.strings中添加相关文本的本地化支持,确保多语言兼容性。 - 优化FeedListView和FeedView以展示最新动态,增强用户交互体验。
240 lines
11 KiB
Swift
240 lines
11 KiB
Swift
import SwiftUI
|
||
import ComposableArchitecture
|
||
import PhotosUI
|
||
|
||
struct CreateFeedView: View {
|
||
let store: StoreOf<CreateFeedFeature>
|
||
@State private var keyboardHeight: CGFloat = 0
|
||
|
||
var body: some View {
|
||
NavigationStack {
|
||
GeometryReader { geometry in
|
||
VStack(spacing: 0) {
|
||
// 背景色
|
||
Color(hex: 0x0C0527)
|
||
.ignoresSafeArea()
|
||
|
||
// 主要内容区域(无ScrollView)
|
||
VStack(spacing: 20) {
|
||
// 内容输入区域
|
||
VStack(alignment: .leading, spacing: 12) {
|
||
// 文本输入框
|
||
ZStack(alignment: .topLeading) {
|
||
RoundedRectangle(cornerRadius: 12)
|
||
.fill(Color.white.opacity(0.1))
|
||
.frame(height: 200) // 高度固定为200
|
||
|
||
if store.content.isEmpty {
|
||
Text(NSLocalizedString("createFeed.enterContent", comment: "Enter Content"))
|
||
.foregroundColor(.white.opacity(0.5))
|
||
.padding(.horizontal, 16)
|
||
.padding(.vertical, 12)
|
||
}
|
||
|
||
TextEditor(text: .init(
|
||
get: { store.content },
|
||
set: { store.send(.contentChanged($0)) }
|
||
))
|
||
.foregroundColor(.white)
|
||
.background(Color.clear)
|
||
.padding(.horizontal, 12)
|
||
.padding(.vertical, 8)
|
||
.scrollContentBackground(.hidden)
|
||
.frame(height: 200) // 高度固定为200
|
||
}
|
||
|
||
// 字符计数
|
||
HStack {
|
||
Spacer()
|
||
Text("\(store.characterCount)/500")
|
||
.font(.system(size: 12))
|
||
.foregroundColor(
|
||
store.characterCount > 500 ? .red : .white.opacity(0.6)
|
||
)
|
||
}
|
||
}
|
||
.padding(.horizontal, 20)
|
||
.padding(.top, 20)
|
||
|
||
// 图片选择区域
|
||
VStack(alignment: .leading, spacing: 12) {
|
||
if !store.processedImages.isEmpty || store.canAddMoreImages {
|
||
ModernImageSelectionGrid(
|
||
images: store.processedImages,
|
||
selectedItems: store.selectedImages,
|
||
canAddMore: store.canAddMoreImages,
|
||
onItemsChanged: { items in
|
||
store.send(.photosPickerItemsChanged(items))
|
||
},
|
||
onRemoveImage: { index in
|
||
store.send(.removeImage(index))
|
||
}
|
||
)
|
||
}
|
||
}
|
||
.padding(.horizontal, 20)
|
||
|
||
// 加载状态
|
||
if store.isLoading {
|
||
HStack {
|
||
ProgressView()
|
||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||
Text(NSLocalizedString("createFeed.processingImages", comment: "Processing images..."))
|
||
.font(.system(size: 14))
|
||
.foregroundColor(.white.opacity(0.8))
|
||
}
|
||
.padding(.top, 10)
|
||
}
|
||
|
||
// 错误提示
|
||
if let error = store.errorMessage {
|
||
Text(error)
|
||
.font(.system(size: 14))
|
||
.foregroundColor(.red)
|
||
.padding(.horizontal, 20)
|
||
.multilineTextAlignment(.center)
|
||
}
|
||
|
||
// 底部间距,确保内容不被键盘遮挡
|
||
Color.clear.frame(height: max(keyboardHeight, geometry.safeAreaInsets.bottom) + 100)
|
||
}
|
||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||
.ignoresSafeArea(.keyboard, edges: .bottom)
|
||
|
||
// 底部发布按钮 - 固定在底部
|
||
VStack {
|
||
Button(action: {
|
||
store.send(.publishButtonTapped)
|
||
}) {
|
||
HStack {
|
||
if store.isLoading {
|
||
ProgressView()
|
||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||
.scaleEffect(0.8)
|
||
Text(NSLocalizedString("createFeed.publishing", comment: "Publishing..."))
|
||
.font(.system(size: 16, weight: .medium))
|
||
.foregroundColor(.white)
|
||
} else {
|
||
Text(NSLocalizedString("createFeed.publish", comment: "Publish"))
|
||
.font(.system(size: 16, weight: .medium))
|
||
.foregroundColor(.white)
|
||
}
|
||
}
|
||
.frame(maxWidth: .infinity)
|
||
.frame(height: 50)
|
||
.background(
|
||
Color(hex: 0x0C0527)
|
||
)
|
||
.cornerRadius(25)
|
||
.disabled(store.isLoading || !store.canPublish)
|
||
.opacity(store.isLoading || !store.canPublish ? 0.6 : 1.0)
|
||
}
|
||
.padding(.horizontal, 20)
|
||
.padding(.bottom, max(keyboardHeight, geometry.safeAreaInsets.bottom) + 20)
|
||
}
|
||
.background(
|
||
Color(hex: 0x0C0527)
|
||
)
|
||
}
|
||
}
|
||
.navigationTitle(NSLocalizedString("createFeed.title", comment: "Image & Text Publish"))
|
||
.navigationBarTitleDisplayMode(.inline)
|
||
.toolbarBackground(.hidden, for: .navigationBar)
|
||
.navigationBarBackButtonHidden(true)
|
||
.toolbar {
|
||
ToolbarItem(placement: .navigationBarLeading) {
|
||
Button(action: {
|
||
store.send(.dismissView)
|
||
}) {
|
||
Image(systemName: "xmark")
|
||
.font(.system(size: 18, weight: .medium))
|
||
.foregroundColor(.white)
|
||
}
|
||
}
|
||
// 移除右上角发布按钮
|
||
}
|
||
}
|
||
.preferredColorScheme(.dark)
|
||
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { notification in
|
||
if let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
|
||
keyboardHeight = keyboardFrame.height
|
||
}
|
||
}
|
||
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in
|
||
keyboardHeight = 0
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
// MARK: - iOS 16+ 图片选择网格组件
|
||
struct ModernImageSelectionGrid: View {
|
||
let images: [UIImage]
|
||
let selectedItems: [PhotosPickerItem]
|
||
let canAddMore: Bool
|
||
let onItemsChanged: ([PhotosPickerItem]) -> Void
|
||
let onRemoveImage: (Int) -> Void
|
||
|
||
private let columns = Array(repeating: GridItem(.flexible(), spacing: 8), count: 3)
|
||
|
||
var body: some View {
|
||
WithPerceptionTracking {
|
||
LazyVGrid(columns: columns, spacing: 8) {
|
||
// 显示已选择的图片
|
||
ForEach(Array(images.enumerated()), id: \.offset) { index, image in
|
||
ZStack(alignment: .topTrailing) {
|
||
Image(uiImage: image)
|
||
.resizable()
|
||
.aspectRatio(contentMode: .fill)
|
||
.frame(height: 100)
|
||
.clipped()
|
||
.cornerRadius(8)
|
||
|
||
// 删除按钮
|
||
Button(action: {
|
||
onRemoveImage(index)
|
||
}) {
|
||
Image(systemName: "xmark.circle.fill")
|
||
.font(.system(size: 20))
|
||
.foregroundColor(.white)
|
||
.background(Color.black.opacity(0.6))
|
||
.clipShape(Circle())
|
||
}
|
||
.padding(4)
|
||
}
|
||
}
|
||
|
||
// 添加图片按钮
|
||
if canAddMore {
|
||
PhotosPicker(
|
||
selection: .init(
|
||
get: { selectedItems },
|
||
set: onItemsChanged
|
||
),
|
||
maxSelectionCount: 9,
|
||
matching: .images
|
||
) {
|
||
RoundedRectangle(cornerRadius: 8)
|
||
.fill(Color.white.opacity(0.1))
|
||
.frame(height: 100)
|
||
.overlay(
|
||
Image(systemName: "plus")
|
||
.font(.system(size: 40))
|
||
.foregroundColor(.white.opacity(0.6))
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - 预览
|
||
//#Preview {
|
||
// CreateFeedView(
|
||
// store: Store(initialState: CreateFeedFeature.State()) {
|
||
// CreateFeedFeature()
|
||
// }
|
||
// )
|
||
//}
|