import SwiftUI import ComposableArchitecture import PhotosUI struct CreateFeedView: View { let store: StoreOf @State private var isKeyboardVisible: Bool = false @FocusState private var isTextEditorFocused: Bool var body: some View { NavigationStack { GeometryReader { geometry in VStack(spacing: 0) { ZStack { // 背景色 Color(hex: 0x0C0527) .ignoresSafeArea() // 主要内容区域 VStack(spacing: 20) { ContentInputSection(store: store, isFocused: $isTextEditorFocused) ImageSelectionSection(store: store) LoadingAndErrorSection(store: store) Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .ignoresSafeArea(.keyboard, edges: .bottom) .background(Color(hex: 0x0C0527)) } // 底部发布按钮 - 只在键盘隐藏时显示 if !isKeyboardVisible { PublishButtonSection(store: store, geometry: geometry, isFocused: $isTextEditorFocused) } } .onTapGesture { isTextEditorFocused = false // 点击空白处收起键盘 } } .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) } } ToolbarItem(placement: .principal) { Text(NSLocalizedString("createFeed.title", comment: "Image & Text Publish")) .font(.system(size: 18, weight: .medium)) .foregroundColor(.white) } // 右上角发布按钮 - 只在键盘显示时出现 ToolbarItem(placement: .navigationBarTrailing) { if isKeyboardVisible { Button(action: { isTextEditorFocused = false // 收起键盘 store.send(.publishButtonTapped) }) { HStack(spacing: 4) { if store.isLoading || store.isUploadingImages { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .white)) .scaleEffect(0.7) } Text(toolbarButtonText) .font(.system(size: 14, weight: .medium)) .foregroundColor(.white) } .padding(.horizontal, 12) .padding(.vertical, 8) .background( LinearGradient( gradient: Gradient(colors: [ Color(hex: 0xF854FC), Color(hex: 0x500FFF) ]), startPoint: .leading, endPoint: .trailing ) ) .cornerRadius(16) } .disabled(store.isLoading || store.isUploadingImages || !store.canPublish) .opacity(toolbarButtonOpacity) } } } } .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { notification in if let _ = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect { DispatchQueue.main.async { isKeyboardVisible = true } } } .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in DispatchQueue.main.async { isKeyboardVisible = false } } .onReceive(NotificationCenter.default.publisher(for: .init("CreateFeedDismiss"))) { _ in store.send(.dismissView) } .onDisappear { isKeyboardVisible = false } } // MARK: - 工具栏按钮计算属性 private var toolbarButtonText: String { if store.isUploadingImages { return "上传中..." } else if store.isLoading { return NSLocalizedString("createFeed.publishing", comment: "Publishing...") } else { return NSLocalizedString("createFeed.publish", comment: "Publish") } } private var toolbarButtonOpacity: Double { store.isLoading || store.isUploadingImages || !store.canPublish ? 0.6 : 1.0 } } // MARK: - 内容输入区域组件 struct ContentInputSection: View { let store: StoreOf @FocusState.Binding var isFocused: Bool var body: some View { VStack(alignment: .leading, spacing: 12) { ZStack(alignment: .topLeading) { RoundedRectangle(cornerRadius: 8) .fill(Color.init(hex: 0x1C143A)) if store.content.isEmpty { Text(NSLocalizedString("createFeed.enterContent", comment: "Enter Content")) .foregroundColor(.white.opacity(0.5)) .padding(.horizontal, 16) .padding(.vertical, 12) } TextEditor(text: textBinding) .foregroundColor(.white) .background(Color.clear) .padding(.horizontal, 12) .padding(.vertical, 8) .scrollContentBackground(.hidden) .frame(height: 200) .focused($isFocused) } // 字符计数 HStack { Spacer() Text("\(store.characterCount)/500") .font(.system(size: 12)) .foregroundColor(characterCountColor) } } .frame(height: 200) .padding(.horizontal, 20) .padding(.top, 20) } // MARK: - 计算属性 private var textBinding: Binding { Binding( get: { store.content }, set: { store.send(.contentChanged($0)) } ) } private var characterCountColor: Color { store.characterCount > 500 ? .red : .white.opacity(0.6) } } // MARK: - 图片选择区域组件 struct ImageSelectionSection: View { let store: StoreOf var body: some View { VStack(alignment: .leading, spacing: 12) { if shouldShowImageSelection { 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) } // MARK: - 计算属性 private var shouldShowImageSelection: Bool { !store.processedImages.isEmpty || store.canAddMoreImages } } // MARK: - 加载和错误状态组件 struct LoadingAndErrorSection: View { let store: StoreOf var body: some View { VStack(spacing: 10) { // 图片上传状态 if store.isUploadingImages { VStack(spacing: 8) { HStack { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .white)) Text(store.uploadStatus) .font(.system(size: 14)) .foregroundColor(.white.opacity(0.8)) } // 上传进度条 ProgressView(value: store.uploadProgress) .progressViewStyle(LinearProgressViewStyle(tint: .blue)) .frame(height: 4) .background(Color.white.opacity(0.2)) .cornerRadius(2) } .padding(.top, 10) } // 内容发布加载状态 if store.isLoading && !store.isUploadingImages { HStack { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .white)) Text(NSLocalizedString("createFeed.publishing", comment: "Publishing...")) .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) } } } } // MARK: - 发布按钮组件 struct PublishButtonSection: View { let store: StoreOf let geometry: GeometryProxy @FocusState.Binding var isFocused: Bool var body: some View { VStack(spacing: 0) { Button(action: { isFocused = false // 收起键盘 store.send(.publishButtonTapped) }) { HStack { if store.isLoading || store.isUploadingImages { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .white)) .scaleEffect(0.8) Text(buttonText) .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: 45) .background( LinearGradient( gradient: Gradient(colors: [ Color(hex: 0xF854FC), Color(hex: 0x500FFF) ]), startPoint: .leading, endPoint: .trailing ) ) .cornerRadius(22.5) .disabled(store.isLoading || store.isUploadingImages || !store.canPublish) .opacity(buttonOpacity) } .padding(.horizontal, 16) .padding(.bottom, 20) // 使用固定间距,不受键盘影响 } .background(Color(hex: 0x0C0527)) } // MARK: - 计算属性 private var buttonOpacity: Double { store.isLoading || store.isUploadingImages || !store.canPublish ? 0.6 : 1.0 } private var buttonText: String { if store.isUploadingImages { return "上传图片中..." } else if store.isLoading { return NSLocalizedString("createFeed.publishing", comment: "Publishing...") } else { return NSLocalizedString("createFeed.publish", comment: "Publish") } } } // 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 { LazyVGrid(columns: columns, spacing: 8) { // 显示已选择的图片 ForEach(Array(images.enumerated()), id: \.offset) { index, image in ImageItemView( image: image, index: index, onRemove: onRemoveImage ) } // 添加图片按钮 if canAddMore { CreateAddImageButton( selectedItems: selectedItems, onItemsChanged: onItemsChanged ) } } } } // MARK: - 图片项组件 struct ImageItemView: View { let image: UIImage let index: Int let onRemove: (Int) -> Void var body: some View { ZStack(alignment: .topTrailing) { Image(uiImage: image) .resizable() .aspectRatio(contentMode: .fill) .frame(width: 100, height: 100) .clipped() .cornerRadius(8) // 删除按钮 Button(action: { onRemove(index) }) { Image(systemName: "xmark.circle.fill") .font(.system(size: 20)) .foregroundColor(.white) .background(Color.black.opacity(0.6)) .clipShape(Circle()) } .padding(4) } } } // MARK: - 添加图片按钮组件 struct CreateAddImageButton: View { let selectedItems: [PhotosPickerItem] let onItemsChanged: ([PhotosPickerItem]) -> Void var body: some View { PhotosPicker( selection: selectionBinding, maxSelectionCount: 9, matching: .images ) { Image("add photo") .frame(width: 100, height: 100) } } // MARK: - 计算属性 private var selectionBinding: Binding<[PhotosPickerItem]> { Binding( get: { selectedItems }, set: onItemsChanged ) } } // MARK: - 预览 //#Preview { // CreateFeedView( // store: Store(initialState: CreateFeedFeature.State()) { // CreateFeedFeature() // } // ) //}