diff --git a/yana/APIs/DynamicsModels.swift b/yana/APIs/DynamicsModels.swift index a0cdc7a..d221cf8 100644 --- a/yana/APIs/DynamicsModels.swift +++ b/yana/APIs/DynamicsModels.swift @@ -319,15 +319,15 @@ struct LikeDynamicRequest: APIRequestProtocol { self.worldId = worldId } - var queryParameters: [String: String]? { nil } + var bodyParameters: [String: Any]? { nil } - var bodyParameters: [String: Any]? { + var queryParameters: [String: String]? { return [ - "dynamicId": dynamicId, - "uid": uid, - "status": status, - "likedUid": likedUid, - "worldId": worldId + "dynamicId": String(dynamicId), + "uid": String(uid), + "status": String(status), + "likedUid": String(likedUid), + "worldId": String(worldId) ] } diff --git a/yana/Features/DetailFeature.swift b/yana/Features/DetailFeature.swift index d8b777b..db3d0a3 100644 --- a/yana/Features/DetailFeature.swift +++ b/yana/Features/DetailFeature.swift @@ -4,6 +4,7 @@ import ComposableArchitecture @Reducer struct DetailFeature { @Dependency(\.apiService) var apiService + @Dependency(\.isPresented) var isPresented @ObservableState struct State: Equatable { @@ -14,6 +15,13 @@ struct DetailFeature { var selectedImageIndex = 0 var selectedImages: [String] = [] + // 新增:当前用户ID状态 + var currentUserId: String? + var isLoadingCurrentUserId = false + + // 新增:是否需要关闭DetailView + var shouldDismiss = false + init(moment: MomentsInfo) { self.moment = moment } @@ -29,12 +37,35 @@ struct DetailFeature { case hideImagePreview case imagePreviewDismissed case onLikeSuccess(Int, Bool) // dynamicId, newLikeState + case dismissView + + // 新增:当前用户ID相关actions + case loadCurrentUserId + case currentUserIdLoaded(String?) } var body: some ReducerOf { Reduce { state, action in switch action { case .onAppear: + // 如果还没有获取过当前用户ID,则开始获取 + if state.currentUserId == nil && !state.isLoadingCurrentUserId { + return .send(.loadCurrentUserId) + } + return .none + + case .loadCurrentUserId: + state.isLoadingCurrentUserId = true + return .run { send in + let userId = await UserInfoManager.getCurrentUserId() + debugInfoSync("🔍 DetailFeature: 获取当前用户ID - \(userId ?? "nil")") + await send(.currentUserIdLoaded(userId)) + } + + case let .currentUserIdLoaded(userId): + state.currentUserId = userId + state.isLoadingCurrentUserId = false + debugInfoSync("✅ DetailFeature: 当前用户ID已加载 - \(userId ?? "nil")") return .none case let .likeDynamic(dynamicId, uid, likedUid, worldId): @@ -118,8 +149,9 @@ struct DetailFeature { case let .deleteResponse(.success(response)): state.isDeleteLoading = false + debugInfoSync("✅ DetailFeature: 动态删除成功") // 删除成功,返回上一页 - return .none + return .send(.dismissView) case let .deleteResponse(.failure(error)): state.isDeleteLoading = false @@ -139,6 +171,11 @@ struct DetailFeature { case .imagePreviewDismissed: state.showImagePreview = false return .none + + case .dismissView: + debugInfoSync("🔍 DetailFeature: 请求关闭DetailView") + state.shouldDismiss = true + return .none } } } diff --git a/yana/Features/FeedListFeature.swift b/yana/Features/FeedListFeature.swift index d218d0a..29bccf2 100644 --- a/yana/Features/FeedListFeature.swift +++ b/yana/Features/FeedListFeature.swift @@ -21,6 +21,8 @@ struct FeedListFeature { // 新增:DetailView相关状态 var showDetail: Bool = false var selectedMoment: MomentsInfo? + // 新增:点赞相关状态 + var likeLoadingDynamicIds: Set = [] } enum Action: Equatable { @@ -37,6 +39,9 @@ struct FeedListFeature { // 新增:DetailView相关Action case showDetail(MomentsInfo) case detailDismissed + // 新增:点赞相关Action + case likeDynamic(Int, Int, Int, Int) // dynamicId, uid, likedUid, worldId + case likeResponse(TaskResult) // 预留后续 Action } @@ -143,6 +148,94 @@ struct FeedListFeature { state.showDetail = false state.selectedMoment = nil return .none + case let .likeDynamic(dynamicId, uid, likedUid, worldId): + // 找到对应的动态并更新点赞状态 + guard let index = state.moments.firstIndex(where: { $0.dynamicId == dynamicId }) else { + return .none + } + + // 添加loading状态 + state.likeLoadingDynamicIds.insert(dynamicId) + + // 获取当前点赞状态 + let currentMoment = state.moments[index] + let status = currentMoment.isLike ? 0 : 1 // 0: 取消点赞, 1: 点赞 + + let request = LikeDynamicRequest( + dynamicId: dynamicId, + uid: uid, + status: status, + likedUid: likedUid, + worldId: worldId + ) + + return .run { [apiService] send in + let result = await TaskResult { + try await apiService.request(request) + } + await send(.likeResponse(result)) + } + + case let .likeResponse(.success(response)): + // 移除loading状态 + if let data = response.data, let success = data.success, success { + // 找到对应的动态并更新点赞状态 + // 注意:这里我们需要从loading状态中找到对应的dynamicId + let loadingDynamicIds = state.likeLoadingDynamicIds + for dynamicId in loadingDynamicIds { + if let index = state.moments.firstIndex(where: { $0.dynamicId == dynamicId }) { + let currentMoment = state.moments[index] + let newLikeState = !currentMoment.isLike + let newLikeCount = data.likeCount ?? currentMoment.likeCount + + // 创建更新后的动态对象 + let updatedMoment = MomentsInfo( + dynamicId: currentMoment.dynamicId, + uid: currentMoment.uid, + nick: currentMoment.nick, + avatar: currentMoment.avatar, + type: currentMoment.type, + content: currentMoment.content, + likeCount: newLikeCount, + isLike: newLikeState, + commentCount: currentMoment.commentCount, + publishTime: currentMoment.publishTime, + worldId: currentMoment.worldId, + status: currentMoment.status, + playCount: currentMoment.playCount, + dynamicResList: currentMoment.dynamicResList, + gender: currentMoment.gender, + squareTop: currentMoment.squareTop, + topicTop: currentMoment.topicTop, + newUser: currentMoment.newUser, + defUser: currentMoment.defUser, + scene: currentMoment.scene, + userVipInfoVO: currentMoment.userVipInfoVO, + headwearPic: currentMoment.headwearPic, + headwearEffect: currentMoment.headwearEffect, + headwearType: currentMoment.headwearType, + headwearName: currentMoment.headwearName, + headwearId: currentMoment.headwearId, + experLevelPic: currentMoment.experLevelPic, + charmLevelPic: currentMoment.charmLevelPic, + isCustomWord: currentMoment.isCustomWord, + labelList: currentMoment.labelList + ) + + state.moments[index] = updatedMoment + break // 找到并更新后退出循环 + } + } + } + + // 移除所有loading状态 + state.likeLoadingDynamicIds.removeAll() + return .none + + case let .likeResponse(.failure(error)): + // 移除loading状态 + state.likeLoadingDynamicIds.removeAll() + return .none } } } diff --git a/yana/Views/Components/OptimizedDynamicCardView.swift b/yana/Views/Components/OptimizedDynamicCardView.swift index bbd3bba..176c91c 100644 --- a/yana/Views/Components/OptimizedDynamicCardView.swift +++ b/yana/Views/Components/OptimizedDynamicCardView.swift @@ -28,14 +28,16 @@ struct OptimizedDynamicCardView: View { public var body: some View { ZStack { - // 背景层 - RoundedRectangle(cornerRadius: 12) - .fill(Color.clear) - .overlay( - RoundedRectangle(cornerRadius: 12) - .stroke(Color.white.opacity(0.1), lineWidth: 1) - ) - .shadow(color: Color(red: 0.43, green: 0.43, blue: 0.43, opacity: 0.34), radius: 10.7, x: 0, y: 1.9) + // 背景层 - 仅在非详情页模式下显示 + if !isDetailMode { + RoundedRectangle(cornerRadius: 12) + .fill(Color.clear) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(Color.white.opacity(0.1), lineWidth: 1) + ) + .shadow(color: Color(red: 0.43, green: 0.43, blue: 0.43, opacity: 0.34), radius: 10.7, x: 0, y: 1.9) + } // 内容层 VStack(alignment: .leading, spacing: 12) { @@ -97,7 +99,7 @@ struct OptimizedDynamicCardView: View { // 互动按钮 HStack(spacing: 20) { - // Like 按钮左对齐 + // Like 按钮与用户名左侧对齐 Button(action: { onLikeTap(moment.dynamicId, moment.uid, moment.uid, moment.worldId) }) { @@ -116,18 +118,13 @@ struct OptimizedDynamicCardView: View { .foregroundColor(moment.isLike ? .red : .white.opacity(0.8)) } .disabled(isLikeLoading) + .padding(.leading, 40 + 8) // 与用户名左侧对齐(头像宽度 + 间距) Spacer() } .padding(.top, 8) } .padding(16) } - .onTapGesture { - // 只有在非详情页模式下才允许点击跳转 - if !isDetailMode { - // 点击事件由父视图处理 - } - } .onAppear { preloadNearbyImages() } diff --git a/yana/Views/DetailView.swift b/yana/Views/DetailView.swift index 08a053c..f54b8b6 100644 --- a/yana/Views/DetailView.swift +++ b/yana/Views/DetailView.swift @@ -3,87 +3,183 @@ import ComposableArchitecture struct DetailView: View { @State var store: StoreOf -// @Environment(\.dismiss) private var dismiss let onLikeSuccess: ((Int, Bool) -> Void)? + let onDismiss: (() -> Void)? // 新增:关闭回调 - init(store: StoreOf, onLikeSuccess: ((Int, Bool) -> Void)? = nil) { + init(store: StoreOf, onLikeSuccess: ((Int, Bool) -> Void)? = nil, onDismiss: (() -> Void)? = nil) { self.store = store self.onLikeSuccess = onLikeSuccess + self.onDismiss = onDismiss } var body: some View { - ScrollView { - VStack(spacing: 0) { - // 使用OptimizedDynamicCardView显示动态内容 - OptimizedDynamicCardView( - moment: store.moment, - allMoments: [store.moment], // 详情页只有一个动态 - currentIndex: 0, - onImageTap: { images, index in - store.send(.showImagePreview(images, index)) + ZStack { + // 背景图片 + Image("bg") + .resizable() + .aspectRatio(contentMode: .fill) + .ignoresSafeArea() + + VStack(spacing: 0) { + // 自定义导航栏 + WithPerceptionTracking { + CustomNavigationBar( + title: NSLocalizedString("detail.title", comment: "Detail page title"), + showDeleteButton: isCurrentUserDynamic, + isDeleteLoading: store.isDeleteLoading, + onBack: { + onDismiss?() // 调用父视图的关闭回调 }, - onLikeTap: { dynamicId, uid, likedUid, worldId in - store.send(.likeDynamic(dynamicId, uid, likedUid, worldId)) - }, - isLikeLoading: store.isLikeLoading, - isDetailMode: true // 详情页模式,点击卡片不跳转 - ) - .padding(.horizontal, 16) - .padding(.top, 16) - } - } - .navigationTitle(NSLocalizedString("detail.title", comment: "Detail page title")) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - // 只有当动态的uid与当前登录用户uid相同时才显示删除按钮 - if isCurrentUserDynamic { - Button(action: { + onDelete: { store.send(.deleteDynamic) - }) { - if store.isDeleteLoading { - ProgressView() - .progressViewStyle(CircularProgressViewStyle(tint: .red)) - .scaleEffect(0.8) - } else { - Image(systemName: "trash") - .foregroundColor(.red) - } } - .disabled(store.isDeleteLoading) + ) + } + .padding(.top, 44) + + // 内容区域 + ScrollView { + VStack(spacing: 0) { + // 使用OptimizedDynamicCardView显示动态内容 + WithPerceptionTracking { + OptimizedDynamicCardView( + moment: store.moment, + allMoments: [store.moment], // 详情页只有一个动态 + currentIndex: 0, + onImageTap: { images, index in + store.send(.showImagePreview(images, index)) + }, + onLikeTap: { dynamicId, uid, likedUid, worldId in + store.send(.likeDynamic(dynamicId, uid, likedUid, worldId)) + }, + isLikeLoading: store.isLikeLoading, + isDetailMode: true // 详情页模式,点击卡片不跳转 + ) + .padding(.horizontal, 16) + .padding(.top, 16) + } } } } + } + .navigationBarHidden(true) .onAppear { + debugInfoSync("🔍 DetailView: onAppear - moment.uid: \(store.moment.uid)") store.send(.onAppear) } -// .onReceive(store.publisher(for: \.moment)) { moment in -// // 监听动态状态变化 -// } + .onChange(of: store.shouldDismiss) { shouldDismiss in + if shouldDismiss { + debugInfoSync("🔍 DetailView: shouldDismiss = true, 调用onDismiss") + onDismiss?() + } + } .fullScreenCover(isPresented: Binding( get: { store.showImagePreview }, set: { _ in store.send(.hideImagePreview) } )) { - ImagePreviewPager( - images: store.selectedImages, - currentIndex: Binding( - get: { store.selectedImageIndex }, - set: { newIndex in - store.send(.showImagePreview(store.selectedImages, newIndex)) + WithPerceptionTracking { + ImagePreviewPager( + images: store.selectedImages, + currentIndex: Binding( + get: { store.selectedImageIndex }, + set: { newIndex in + store.send(.showImagePreview(store.selectedImages, newIndex)) + } + ), + onClose: { + store.send(.imagePreviewDismissed) } - ), - onClose: { - store.send(.imagePreviewDismissed) - } - ) + ) + } } } // 判断是否为当前用户的动态 private var isCurrentUserDynamic: Bool { - // 暂时返回false,避免异步调用问题 - // TODO: 实现异步获取用户ID的逻辑 - return false + // 使用store中的当前用户ID进行判断 + guard let currentUserId = store.currentUserId, + let currentUserIdInt = Int(currentUserId) else { + debugInfoSync("🔍 DetailView: 无法获取当前用户ID - currentUserId: \(store.currentUserId ?? "nil")") + return false + } + let isCurrentUser = store.moment.uid == currentUserIdInt + debugInfoSync("🔍 DetailView: 动态用户判断 - moment.uid: \(store.moment.uid), currentUserId: \(currentUserIdInt), isCurrentUser: \(isCurrentUser)") + return isCurrentUser + } +} + +// MARK: - CustomNavigationBar +struct CustomNavigationBar: View { + let title: String + let showDeleteButton: Bool + let isDeleteLoading: Bool + let onBack: () -> Void + let onDelete: () -> Void + + var body: some View { + HStack { + // 返回按钮 + Button(action: onBack) { + Image(systemName: "chevron.left") + .font(.system(size: 20, weight: .medium)) + .foregroundColor(.white) + .frame(width: 44, height: 44) + .background(Color.black.opacity(0.3)) + .clipShape(Circle()) + } + + Spacer() + + // 标题 + Text(title) + .font(.system(size: 18, weight: .semibold)) + .foregroundColor(.white) + .shadow(color: .black.opacity(0.5), radius: 2, x: 0, y: 1) + + Spacer() + + // 删除按钮(仅在需要时显示) + WithPerceptionTracking { + if showDeleteButton { + Button(action: onDelete) { + if isDeleteLoading { + ProgressView() + .progressViewStyle(CircularProgressViewStyle(tint: .white)) + .scaleEffect(0.8) + .frame(width: 44, height: 44) + .background(Color.red.opacity(0.8)) + .clipShape(Circle()) + } else { + Image(systemName: "trash") + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.white) + .frame(width: 44, height: 44) + .background(Color.red.opacity(0.8)) + .clipShape(Circle()) + } + } + .disabled(isDeleteLoading) + } else { + // 占位,保持标题居中 + Color.clear + .frame(width: 44, height: 44) + } + } + } + .padding(.horizontal, 16) + .padding(.top, 8) + .padding(.bottom, 12) + .background( + LinearGradient( + gradient: Gradient(colors: [ + Color.black.opacity(0.4), + Color.black.opacity(0.2), + Color.clear + ]), + startPoint: .top, + endPoint: .bottom + ) + ) } } diff --git a/yana/Views/FeedListView.swift b/yana/Views/FeedListView.swift index d32e989..660926a 100644 --- a/yana/Views/FeedListView.swift +++ b/yana/Views/FeedListView.swift @@ -80,10 +80,12 @@ struct MomentCardView: View { 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) { @@ -92,7 +94,9 @@ struct MomentCardView: View { allMoments: allMoments, currentIndex: index, onImageTap: onImageTap, - onLikeTap: { _, _,_,_ in } + onLikeTap: onLikeTap, + isLikeLoading: isLikeLoading, + isDetailMode: false ) .onTapGesture { onTap() @@ -117,7 +121,9 @@ struct MomentsListView: View { let isLoadingMore: Bool let onImageTap: ([String], Int) -> Void let onMomentTap: (MomentsInfo) -> Void + let onLikeTap: (Int, Int, Int, Int) -> Void let onLoadMore: () -> Void + let likeLoadingDynamicIds: Set var body: some View { ScrollView { @@ -131,10 +137,12 @@ struct MomentsListView: View { onTap: { onMomentTap(moment) }, + onLikeTap: onLikeTap, onLoadMore: onLoadMore, isLastItem: index == moments.count - 1, hasMore: hasMore, - isLoadingMore: isLoadingMore + isLoadingMore: isLoadingMore, + isLikeLoading: likeLoadingDynamicIds.contains(moment.dynamicId) ) } @@ -182,9 +190,13 @@ struct FeedListContentView: View { onMomentTap: { moment in viewStore.send(.showDetail(moment)) }, + onLikeTap: { dynamicId, uid, likedUid, worldId in + viewStore.send(.likeDynamic(dynamicId, uid, likedUid, worldId)) + }, onLoadMore: { viewStore.send(.loadMore) - } + }, + likeLoadingDynamicIds: viewStore.likeLoadingDynamicIds ) } } @@ -263,12 +275,7 @@ struct FeedListView: View { // 新增:DetailView导航 .navigationDestination(isPresented: viewStore.binding( get: \.showDetail, - send: { isPresented in - if !isPresented { - return .detailDismissed - } - return .detailDismissed - } + send: { _ in .detailDismissed } )) { if let selectedMoment = viewStore.selectedMoment { DetailView( @@ -276,6 +283,9 @@ struct FeedListView: View { initialState: DetailFeature.State(moment: selectedMoment) ) { DetailFeature() + }, + onDismiss: { + viewStore.send(.detailDismissed) } ) }