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 LoadingView: 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 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 @Binding var previewItem: PreviewItem? @Binding var previewCurrentIndex: Int var body: some View { WithViewStore(self.store, observe: { $0 }) { viewStore in if viewStore.isLoading { LoadingView() } else if let error = viewStore.error { ErrorView(error: error) } else if viewStore.moments.isEmpty { EmptyView() } else { MomentsListView( moments: viewStore.moments, hasMore: viewStore.hasMore, isLoadingMore: viewStore.isLoadingMore, onImageTap: { images, tappedIndex in previewCurrentIndex = tappedIndex previewItem = PreviewItem(images: images, index: tappedIndex) }, onMomentTap: { moment in viewStore.send(.showDetail(moment)) }, onLikeTap: { dynamicId, uid, likedUid, worldId in viewStore.send(.likeDynamic(dynamicId, uid, likedUid, worldId)) }, onLoadMore: { viewStore.send(.loadMore) }, onRefresh: { viewStore.send(.reload) }, likeLoadingDynamicIds: viewStore.likeLoadingDynamicIds ) } } } } struct FeedListView: View { let store: StoreOf // 新增:图片预览状态 @State private var previewItem: PreviewItem? = nil @State private var previewCurrentIndex: Int = 0 var body: some View { WithViewStore(self.store, observe: { $0 }) { viewStore in WithPerceptionTracking { GeometryReader { geometry in ZStack { // 背景 BackgroundView() VStack(alignment: .center, spacing: 0) { // 顶部栏 TopBarView { viewStore.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() } .frame(maxWidth: .infinity, alignment: .top) } // 添加API Loading和错误处理视图 APILoadingEffectView() } .onAppear { viewStore.send(.onAppear) } .onReceive(NotificationCenter.default.publisher(for: Notification.Name("reloadFeedList"))) { _ in viewStore.send(.reload) } .sheet(isPresented: viewStore.binding( get: \.isEditFeedPresented, send: { $0 ? .editFeedButtonTapped : .editFeedDismissed } )) { EditFeedView( onDismiss: { viewStore.send(.editFeedDismissed) }, store: Store( initialState: EditFeedFeature.State() ) { EditFeedFeature() } ) } // 新增:图片预览弹窗 .fullScreenCover(item: $previewItem) { item in ImagePreviewPager(images: item.images, currentIndex: $previewCurrentIndex) { previewItem = nil } } // 新增:DetailView导航 .navigationDestination(isPresented: viewStore.binding( get: \.showDetail, send: { _ in .detailDismissed } )) { if let selectedMoment = viewStore.selectedMoment { DetailView( store: Store( initialState: DetailFeature.State(moment: selectedMoment) ) { DetailFeature() }, onDismiss: { viewStore.send(.detailDismissed) } ) } } } } } }