Files
e-party-iOS/yana/Views/FeedListView.swift
edwinQQQ de2f05f545 feat: 新增动态点赞与删除功能
- 在APIEndpoints中新增动态点赞和删除端点。
- 实现LikeDynamicRequest和DeleteDynamicRequest结构体,支持动态点赞和删除请求。
- 在DetailFeature中添加点赞和删除动态的逻辑,提升用户交互体验。
- 更新FeedListFeature以支持动态详情视图的展示,增强用户体验。
- 新增DetailView以展示动态详情,包含点赞和删除功能。
2025-07-28 11:23:34 +08:00

287 lines
9.4 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(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 onLoadMore: () -> Void
let isLastItem: Bool
let hasMore: Bool
let isLoadingMore: Bool
var body: some View {
VStack(spacing: 16) {
OptimizedDynamicCardView(
moment: moment,
allMoments: allMoments,
currentIndex: index,
onImageTap: onImageTap,
onLikeTap: { _, _,_,_ in }
)
.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 onLoadMore: () -> Void
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)
},
onLoadMore: onLoadMore,
isLastItem: index == moments.count - 1,
hasMore: hasMore,
isLoadingMore: isLoadingMore
)
}
//
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 {
onLoadMore()
}
}
}
// 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))
},
onLoadMore: {
viewStore.send(.loadMore)
}
)
}
}
}
}
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)
}
}
.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: { isPresented in
if !isPresented {
return .detailDismissed
}
return .detailDismissed
}
)) {
if let selectedMoment = viewStore.selectedMoment {
DetailView(
store: Store(
initialState: DetailFeature.State(moment: selectedMoment)
) {
DetailFeature()
}
)
}
}
}
}
}
}