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

293 lines
9.7 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 })) {
let createFeedStore = Store(
initialState: CreateFeedFeature.State()
) {
CreateFeedFeature()
}
CreateFeedView(store: createFeedStore)
.onReceive(NotificationCenter.default.publisher(for: .init("CreateFeedPublishSuccess"))) { _ in
store.send(.createFeedPublishSuccess)
}
.onReceive(NotificationCenter.default.publisher(for: .init("CreateFeedDismiss"))) { _ in
store.send(.editFeedDismissed)
}
}
//
.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
}
}
}
}
}