Files
e-party-iOS/yana/Views/CreateFeedView.swift
edwinQQQ 57a8b833eb feat: 更新CreateFeedFeature和FeedListFeature以增强发布和关闭功能
- 修改CreateFeedFeature中的发布逻辑,确保在发布成功时同时发送关闭通知。
- 更新FeedListFeature以在创建Feed成功时触发刷新并关闭编辑页面。
- 优化CreateFeedView中的键盘管理和通知处理,提升用户体验。
2025-07-31 16:44:49 +08:00

427 lines
15 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 SwiftUI
import ComposableArchitecture
import PhotosUI
struct CreateFeedView: View {
let store: StoreOf<CreateFeedFeature>
@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<CreateFeedFeature>
@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<String> {
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<CreateFeedFeature>
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<CreateFeedFeature>
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<CreateFeedFeature>
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()
// }
// )
//}