Files
e-party-iOS/yana/Views/FeedListView.swift
edwinQQQ 1f17960b8d feat: 新增CreateFeedView优化任务总结文档及相关功能实现
- 在CreateFeedView中优化发布按钮样式,增加圆角背景和渐变色。
- 移除内容输入区域的深灰色背景,提升UI体验。
- 实现点击发布按钮时自动收起键盘功能。
- 添加发布成功通知机制,确保外层刷新列表数据。
- 更新相关Feature以支持跨Feature通信和状态管理。
2025-07-31 14:23:15 +08:00

290 lines
9.5 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
// MARK: - BackgroundView
struct BackgroundView: View {
var body: some View {
Image("bg")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.clipped()
.ignoresSafeArea(.all)
}
}
// MARK: - TopBarView
struct TopBarView: View {
let onEditTapped: () -> Void
var body: some View {
ZStack {
HStack {
Spacer(minLength: 0)
Text(LocalizedString("feedList.title", comment: "Enjoy your Life Time"))
.font(.system(size: 22, weight: .semibold))
.foregroundColor(.white)
.frame(maxWidth: .infinity, alignment: .center)
Spacer(minLength: 0)
}
HStack {
Spacer(minLength: 0)
Button(action: onEditTapped) {
Image("add icon")
.resizable()
.frame(width: 40, height: 40)
}
}
}
.padding(.horizontal, 20)
}
}
// MARK: - LoadingView
private struct FeedListLoadingView: View {
var body: some View {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.padding(.top, 20)
}
}
// MARK: - ErrorView
struct ErrorView: View {
let error: String
var body: some View {
Text(error)
.font(.system(size: 14))
.foregroundColor(.red)
.multilineTextAlignment(.center)
.padding(.horizontal, 20)
.padding(.top, 20)
}
}
// MARK: - EmptyView
struct EmptyView: View {
var body: some View {
Text(LocalizedString("feedList.empty", comment: "暂无动态"))
.font(.system(size: 16))
.foregroundColor(.white.opacity(0.7))
.padding(.top, 20)
}
}
// MARK: - MomentCardView
struct MomentCardView: View {
let moment: MomentsInfo
let allMoments: [MomentsInfo]
let index: Int
let onImageTap: ([String], Int) -> Void
let onTap: () -> Void
let onLikeTap: (Int, Int, Int, Int) -> Void
let onLoadMore: () -> Void
let isLastItem: Bool
let hasMore: Bool
let isLoadingMore: Bool
let isLikeLoading: Bool
var body: some View {
VStack(spacing: 16) {
OptimizedDynamicCardView(
moment: moment,
allMoments: allMoments,
currentIndex: index,
onImageTap: onImageTap,
onLikeTap: onLikeTap,
onCardTap: onTap,
isDetailMode: false,
isLikeLoading: isLikeLoading
)
//
if isLastItem && hasMore && !isLoadingMore {
Color.clear
.frame(height: 1)
.onAppear {
onLoadMore()
}
}
}
}
}
// MARK: - MomentsListView
struct MomentsListView: View {
let moments: [MomentsInfo]
let hasMore: Bool
let isLoadingMore: Bool
let onImageTap: ([String], Int) -> Void
let onMomentTap: (MomentsInfo) -> Void
let onLikeTap: (Int, Int, Int, Int) -> Void
let onLoadMore: () -> Void
let onRefresh: () -> Void
let likeLoadingDynamicIds: Set<Int>
var body: some View {
ScrollView {
LazyVStack(spacing: 16) {
ForEach(Array(moments.enumerated()), id: \.element.dynamicId) { index, moment in
MomentCardView(
moment: moment,
allMoments: moments,
index: index,
onImageTap: onImageTap,
onTap: {
onMomentTap(moment)
},
onLikeTap: onLikeTap,
onLoadMore: onLoadMore,
isLastItem: index == moments.count - 1,
hasMore: hasMore,
isLoadingMore: isLoadingMore,
isLikeLoading: likeLoadingDynamicIds.contains(moment.dynamicId)
)
}
//
if isLoadingMore {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.padding(.vertical, 8)
}
//
Color.clear.frame(height: 120)
}
.padding(.horizontal, 16)
.padding(.top, 10)
.padding(.bottom, 20)
}
.refreshable {
onRefresh()
}
}
}
// MARK: - FeedListContentView
struct FeedListContentView: View {
let store: StoreOf<FeedListFeature>
@Binding var previewItem: PreviewItem?
@Binding var previewCurrentIndex: Int
var body: some View {
if store.isLoading {
FeedListLoadingView()
} else if let error = store.error {
ErrorView(error: error)
} else if store.moments.isEmpty {
EmptyView()
} else {
MomentsListView(
moments: store.moments,
hasMore: store.hasMore,
isLoadingMore: store.isLoadingMore,
onImageTap: { images, tappedIndex in
previewCurrentIndex = tappedIndex
previewItem = PreviewItem(images: images, index: tappedIndex)
},
onMomentTap: { moment in
store.send(.showDetail(moment))
},
onLikeTap: { dynamicId, uid, likedUid, worldId in
store.send(.likeDynamic(dynamicId, uid, likedUid, worldId))
},
onLoadMore: {
store.send(.loadMore)
},
onRefresh: {
store.send(.reload)
},
likeLoadingDynamicIds: store.likeLoadingDynamicIds
)
}
}
}
struct FeedListView: View {
let store: StoreOf<FeedListFeature>
//
@State private var previewItem: PreviewItem? = nil
@State private var previewCurrentIndex: Int = 0
var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
GeometryReader { geometry in
ZStack {
//
BackgroundView()
VStack(alignment: .center, spacing: 0) {
//
TopBarView {
store.send(.editFeedButtonTapped)
}
//
Image("Volume")
.frame(width: 56, height: 41)
.padding(.top, 16)
Text(LocalizedString("feedList.slogan", comment: "The disease is like a cruel ruler,\nand time is our most precious treasure.\nEvery moment we live is a victory\nagainst the inevitable."))
.font(.system(size: 16))
.multilineTextAlignment(.leading)
.foregroundColor(.white.opacity(0.9))
.padding(.horizontal, 30)
.padding(.bottom, 30)
//
FeedListContentView(
store: store,
previewItem: $previewItem,
previewCurrentIndex: $previewCurrentIndex
)
Spacer()
}
}
}
.onAppear {
store.send(.onAppear)
}
.refreshable {
store.send(.reload)
}
//
.sheet(isPresented: viewStore.binding(get: \.isEditFeedPresented, send: { _ in .editFeedDismissed })) {
CreateFeedView(
store: Store(
initialState: CreateFeedFeature.State()
) {
CreateFeedFeature()
}
)
.onReceive(NotificationCenter.default.publisher(for: .init("CreateFeedPublishSuccess"))) { _ in
store.send(.createFeedPublishSuccess)
}
}
//
.navigationDestination(isPresented: viewStore.binding(get: \.showDetail, send: { _ in .detailDismissed })) {
if let selectedMoment = viewStore.selectedMoment {
DetailView(
store: Store(
initialState: DetailFeature.State(moment: selectedMoment)
) {
DetailFeature()
}
)
}
}
//
.fullScreenCover(item: $previewItem) { item in
ImagePreviewPager(images: item.images as [String], currentIndex: $previewCurrentIndex) {
previewItem = nil
}
}
}
}
}