
- 在CreateFeedView中添加视图消失时重置键盘状态的逻辑,提升用户体验。 - 在DetailView中调整顶部内边距,改善布局效果。 - 在FeedListView中新增刷新功能的回调,增强动态加载体验。 - 在MainView中为底部导航栏留出空间并固定在底部,优化界面布局。
304 lines
10 KiB
Swift
304 lines
10 KiB
Swift
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(NSLocalizedString("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(NSLocalizedString("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,
|
||
isDetailMode: false,
|
||
isLikeLoading: isLikeLoading
|
||
)
|
||
.onTapGesture {
|
||
onTap()
|
||
}
|
||
|
||
// 上拉加载更多触发点
|
||
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?
|
||
|
||
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
|
||
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<FeedListFeature>
|
||
// 新增:图片预览状态
|
||
@State private var previewItem: PreviewItem? = nil
|
||
|
||
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(NSLocalizedString("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
|
||
)
|
||
|
||
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: .constant(item.index)) {
|
||
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)
|
||
}
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|