
- 更新Yana项目文档,调整适用版本至iOS 17,确保与最新开发环境兼容。 - 在多个视图中重构代码,优化状态管理和视图逻辑,提升用户体验。 - 添加默认初始化器以简化状态管理,确保各个Feature的状态一致性。 - 更新视图组件,移除不必要的硬编码,增强代码可读性和维护性。 - 修复多个视图中的逻辑错误,确保功能正常运行。
323 lines
10 KiB
Swift
323 lines
10 KiB
Swift
import SwiftUI
|
|
import ComposableArchitecture
|
|
import PhotosUI
|
|
|
|
struct EditFeedView: View {
|
|
let onDismiss: () -> Void
|
|
let store: StoreOf<EditFeedFeature>
|
|
@State private var isKeyboardVisible = false
|
|
private let maxCount = 500
|
|
|
|
private func hideKeyboard() {
|
|
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
|
}
|
|
|
|
var body: some View {
|
|
WithPerceptionTracking {
|
|
GeometryReader { geometry in
|
|
ZStack {
|
|
backgroundView
|
|
mainContent(geometry: geometry)
|
|
if store.isUploadingImages {
|
|
uploadingImagesOverlay(progress: store.imageUploadProgress)
|
|
} else if store.isLoading {
|
|
loadingOverlay
|
|
}
|
|
}
|
|
.contentShape(Rectangle())
|
|
.onTapGesture {
|
|
if isKeyboardVisible {
|
|
hideKeyboard()
|
|
}
|
|
}
|
|
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { _ in
|
|
withAnimation(.easeInOut(duration: 0.3)) {
|
|
isKeyboardVisible = true
|
|
}
|
|
}
|
|
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in
|
|
withAnimation(.easeInOut(duration: 0.3)) {
|
|
isKeyboardVisible = false
|
|
}
|
|
}
|
|
}
|
|
.navigationBarHidden(true)
|
|
.onAppear {
|
|
store.send(.clearError)
|
|
}
|
|
.onChange(of: store.shouldDismiss) {
|
|
if store.shouldDismiss {
|
|
onDismiss()
|
|
}
|
|
}
|
|
.photosPicker(
|
|
isPresented: Binding(
|
|
get: { store.showPhotosPicker },
|
|
set: { _ in store.send(.photosPickerDismissed) }
|
|
),
|
|
selection: Binding(
|
|
get: { store.selectedPhotoItems },
|
|
set: { store.send(.photosPickerItemsChanged($0)) }
|
|
),
|
|
maxSelectionCount: 9,
|
|
matching: .images
|
|
)
|
|
.alert("删除图片", isPresented: Binding(
|
|
get: { store.showDeleteImageAlert },
|
|
set: { _ in store.send(.deleteImageAlertDismissed) }
|
|
)) {
|
|
Button("删除", role: .destructive) {
|
|
if let indexToDelete = store.imageToDeleteIndex {
|
|
store.send(.removeImage(indexToDelete))
|
|
}
|
|
}
|
|
Button("取消", role: .cancel) {
|
|
store.send(.deleteImageAlertDismissed)
|
|
}
|
|
} message: {
|
|
Text("确定要删除这张图片吗?")
|
|
}
|
|
}
|
|
}
|
|
|
|
private var backgroundView: some View {
|
|
Color(hex: 0x0C0527)
|
|
.ignoresSafeArea()
|
|
}
|
|
|
|
private func mainContent(geometry: GeometryProxy) -> some View {
|
|
WithPerceptionTracking {
|
|
VStack(spacing: 0) {
|
|
topNavigationBar
|
|
|
|
ScrollView {
|
|
VStack(spacing: 20) {
|
|
textInputSection
|
|
imageSelectionSection
|
|
publishButton
|
|
}
|
|
.padding(.horizontal, 20)
|
|
.padding(.bottom, 40)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private var topNavigationBar: some View {
|
|
WithPerceptionTracking {
|
|
HStack {
|
|
Button(action: {
|
|
store.send(.clearDismissFlag)
|
|
onDismiss()
|
|
}) {
|
|
Image(systemName: "xmark")
|
|
.font(.system(size: 20, weight: .medium))
|
|
.foregroundColor(.white)
|
|
.frame(width: 44, height: 44)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Text("编辑动态")
|
|
.font(.system(size: 18, weight: .medium))
|
|
.foregroundColor(.white)
|
|
|
|
Spacer()
|
|
|
|
Color.clear
|
|
.frame(width: 44, height: 44)
|
|
}
|
|
.padding(.horizontal, 16)
|
|
.padding(.top, 8)
|
|
.padding(.bottom, 16)
|
|
}
|
|
}
|
|
|
|
private var textInputSection: some View {
|
|
WithPerceptionTracking {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text("分享你的想法...")
|
|
.font(.system(size: 16, weight: .medium))
|
|
.foregroundColor(.white)
|
|
|
|
TextEditor(text: Binding(
|
|
get: { store.content },
|
|
set: { store.send(.contentChanged($0)) }
|
|
))
|
|
.font(.system(size: 16))
|
|
.foregroundColor(.white)
|
|
.background(Color.clear)
|
|
.frame(minHeight: 120)
|
|
.padding(12)
|
|
.background(Color.white.opacity(0.1))
|
|
.cornerRadius(12)
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 12)
|
|
.stroke(Color.white.opacity(0.2), lineWidth: 1)
|
|
)
|
|
|
|
HStack {
|
|
Spacer()
|
|
Text("\(store.content.count)/\(maxCount)")
|
|
.font(.system(size: 12))
|
|
.foregroundColor(.white.opacity(0.6))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private var imageSelectionSection: some View {
|
|
WithPerceptionTracking {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text("添加图片")
|
|
.font(.system(size: 16, weight: .medium))
|
|
.foregroundColor(.white)
|
|
|
|
ImageGrid(
|
|
images: store.processedImages,
|
|
onRemoveImage: { index in
|
|
store.send(.showDeleteImageAlert(index))
|
|
},
|
|
onAddImage: {
|
|
store.send(.addImageButtonTapped)
|
|
}
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var publishButton: some View {
|
|
WithPerceptionTracking {
|
|
Button(action: {
|
|
store.send(.publishButtonTapped)
|
|
}) {
|
|
if store.isLoading {
|
|
ProgressView()
|
|
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
|
.scaleEffect(1.2)
|
|
} else {
|
|
Text("发布")
|
|
.font(.system(size: 16, weight: .medium))
|
|
.foregroundColor(.white)
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.vertical, 16)
|
|
.background(store.content.isEmpty ? Color.gray : Color.blue)
|
|
.cornerRadius(12)
|
|
.disabled(store.isLoading || store.content.isEmpty)
|
|
}
|
|
}
|
|
|
|
private func uploadingImagesOverlay(progress: Double) -> some View {
|
|
WithPerceptionTracking {
|
|
ZStack {
|
|
Color.black.opacity(0.5)
|
|
.ignoresSafeArea()
|
|
|
|
VStack(spacing: 16) {
|
|
ProgressView(value: progress)
|
|
.progressViewStyle(LinearProgressViewStyle(tint: .white))
|
|
.frame(width: 200)
|
|
|
|
Text("上传图片中... \(Int(progress * 100))%")
|
|
.font(.system(size: 16))
|
|
.foregroundColor(.white)
|
|
}
|
|
.padding(24)
|
|
.background(Color.black.opacity(0.8))
|
|
.cornerRadius(12)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var loadingOverlay: some View {
|
|
WithPerceptionTracking {
|
|
ZStack {
|
|
Color.black.opacity(0.5)
|
|
.ignoresSafeArea()
|
|
|
|
ProgressView()
|
|
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
|
.scaleEffect(1.5)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 简化的图片网格组件
|
|
struct ImageGrid: View {
|
|
let images: [UIImage]
|
|
let onRemoveImage: (Int) -> Void
|
|
let onAddImage: () -> 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
|
|
ImageGridItem(
|
|
image: image,
|
|
onRemove: { onRemoveImage(index) }
|
|
)
|
|
}
|
|
|
|
if images.count < 9 {
|
|
AddImageButton(onTap: onAddImage)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 图片网格项组件
|
|
struct ImageGridItem: View {
|
|
let image: UIImage
|
|
let onRemove: () -> Void
|
|
|
|
var body: some View {
|
|
ZStack(alignment: .topTrailing) {
|
|
Image(uiImage: image)
|
|
.resizable()
|
|
.aspectRatio(contentMode: .fill)
|
|
.frame(height: 100)
|
|
.clipped()
|
|
.cornerRadius(8)
|
|
|
|
Button(action: onRemove) {
|
|
Image(systemName: "xmark.circle.fill")
|
|
.font(.system(size: 20))
|
|
.foregroundColor(.white)
|
|
.background(Color.black.opacity(0.5))
|
|
.clipShape(Circle())
|
|
}
|
|
.padding(4)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 添加图片按钮组件
|
|
struct AddImageButton: View {
|
|
let onTap: () -> Void
|
|
|
|
var body: some View {
|
|
Button(action: onTap) {
|
|
VStack {
|
|
Image(systemName: "plus")
|
|
.font(.system(size: 24))
|
|
.foregroundColor(.white.opacity(0.7))
|
|
Text("添加")
|
|
.font(.system(size: 12))
|
|
.foregroundColor(.white.opacity(0.7))
|
|
}
|
|
.frame(height: 100)
|
|
.frame(maxWidth: .infinity)
|
|
.background(Color.white.opacity(0.1))
|
|
.cornerRadius(8)
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 8)
|
|
.stroke(Color.white.opacity(0.2), lineWidth: 1)
|
|
)
|
|
}
|
|
}
|
|
}
|