feat: 新增动态点赞与删除功能
- 在APIEndpoints中新增动态点赞和删除端点。 - 实现LikeDynamicRequest和DeleteDynamicRequest结构体,支持动态点赞和删除请求。 - 在DetailFeature中添加点赞和删除动态的逻辑,提升用户交互体验。 - 更新FeedListFeature以支持动态详情视图的展示,增强用户体验。 - 新增DetailView以展示动态详情,包含点赞和删除功能。
This commit is contained in:
@@ -1,6 +1,196 @@
|
||||
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>
|
||||
// 新增:图片预览状态
|
||||
@@ -10,137 +200,86 @@ struct FeedListView: View {
|
||||
WithViewStore(self.store, observe: { $0 }) { viewStore in
|
||||
WithPerceptionTracking {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
// 背景图片
|
||||
Image("bg")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.clipped()
|
||||
.ignoresSafeArea(.all)
|
||||
VStack(alignment: .center, spacing: 0) {
|
||||
// 顶部栏
|
||||
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: {
|
||||
viewStore.send(.editFeedButtonTapped)
|
||||
}) {
|
||||
Image("add icon")
|
||||
.resizable()
|
||||
.frame(width: 40, height: 40)
|
||||
}
|
||||
}
|
||||
}
|
||||
ZStack {
|
||||
// 背景
|
||||
BackgroundView()
|
||||
|
||||
.padding(.horizontal, 20)
|
||||
// 其他内容
|
||||
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)
|
||||
// 新增:动态内容列表
|
||||
if viewStore.isLoading {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||
.padding(.top, 20)
|
||||
} else if let error = viewStore.error {
|
||||
Text(error)
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(.red)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.top, 20)
|
||||
} else if viewStore.moments.isEmpty {
|
||||
Text(NSLocalizedString("feedList.empty", comment: "暂无动态"))
|
||||
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))
|
||||
.foregroundColor(.white.opacity(0.7))
|
||||
.padding(.top, 20)
|
||||
} else {
|
||||
ScrollView {
|
||||
WithPerceptionTracking {
|
||||
LazyVStack(spacing: 16) {
|
||||
ForEach(Array(viewStore.moments.enumerated()), id: \ .element.dynamicId) { index, moment in
|
||||
OptimizedDynamicCardView(
|
||||
moment: moment,
|
||||
allMoments: viewStore.moments,
|
||||
currentIndex: index,
|
||||
onImageTap: { images, tappedIndex in
|
||||
previewItem = PreviewItem(images: images, index: tappedIndex)
|
||||
}
|
||||
)
|
||||
// 上拉加载更多触发点
|
||||
if index == viewStore.moments.count - 1, viewStore.hasMore, !viewStore.isLoadingMore {
|
||||
Color.clear
|
||||
.frame(height: 1)
|
||||
.onAppear {
|
||||
viewStore.send(.loadMore)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 加载更多指示器
|
||||
if viewStore.isLoadingMore {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
// 新增底部间距
|
||||
Color.clear.frame(height: 120)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, 20)
|
||||
}
|
||||
}
|
||||
.refreshable {
|
||||
viewStore.send(.reload)
|
||||
}
|
||||
.multilineTextAlignment(.leading)
|
||||
.foregroundColor(.white.opacity(0.9))
|
||||
.padding(.horizontal, 30)
|
||||
.padding(.bottom, 30)
|
||||
|
||||
// 动态内容列表
|
||||
FeedListContentView(
|
||||
store: store,
|
||||
previewItem: $previewItem
|
||||
)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
.frame(maxWidth: .infinity, alignment: .top)
|
||||
}
|
||||
.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()
|
||||
.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()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
// 新增:图片预览弹窗
|
||||
.fullScreenCover(item: $previewItem) { item in
|
||||
ImagePreviewPager(images: item.images, currentIndex: .constant(item.index)) {
|
||||
previewItem = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user