
- 在MePage和MomentListHomePage中新增动态点击事件,支持打开动态详情页。 - 创建MomentDetailPage视图,展示动态详细信息,包括用户信息、动态内容和互动按钮。 - 实现MomentDetailViewModel,管理动态详情页的状态和点赞逻辑。 - 更新MomentListItem组件,添加整体点击回调,提升用户交互体验。 - 优化背景视图组件,确保一致的视觉效果。
246 lines
10 KiB
Swift
246 lines
10 KiB
Swift
import SwiftUI
|
||
|
||
// MARK: - MomentDetailPage
|
||
|
||
struct MomentDetailPage: View {
|
||
@StateObject private var viewModel: MomentDetailViewModel
|
||
let onClose: () -> Void
|
||
|
||
init(moment: MomentsInfo, onClose: @escaping () -> Void) {
|
||
_viewModel = StateObject(wrappedValue: MomentDetailViewModel(moment: moment))
|
||
self.onClose = onClose
|
||
}
|
||
|
||
var body: some View {
|
||
ZStack {
|
||
// 背景
|
||
LoginBackgroundView()
|
||
.ignoresSafeArea()
|
||
|
||
VStack(spacing: 0) {
|
||
// 顶部导航栏
|
||
HStack {
|
||
Button {
|
||
onClose()
|
||
} label: {
|
||
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(LocalizedString("detail.title", comment: "Detail page title"))
|
||
.font(.system(size: 18, weight: .semibold))
|
||
.foregroundColor(.white)
|
||
.shadow(color: .black.opacity(0.5), radius: 2, x: 0, y: 1)
|
||
|
||
Spacer()
|
||
|
||
// 占位,保持标题居中
|
||
Color.clear
|
||
.frame(width: 44, height: 44)
|
||
}
|
||
.padding(.horizontal, 16)
|
||
.safeAreaPadding(.top, 60)
|
||
.padding(.bottom, 12)
|
||
.background(
|
||
LinearGradient(
|
||
gradient: Gradient(colors: [
|
||
Color.black.opacity(0.4),
|
||
Color.black.opacity(0.2),
|
||
Color.clear
|
||
]),
|
||
startPoint: .top,
|
||
endPoint: .bottom
|
||
)
|
||
)
|
||
|
||
// 内容区域
|
||
ScrollView {
|
||
VStack(alignment: .leading, spacing: 12) {
|
||
// 用户信息
|
||
HStack(alignment: .top) {
|
||
// 头像
|
||
CachedAsyncImage(url: viewModel.moment.avatar) { image in
|
||
image
|
||
.resizable()
|
||
.aspectRatio(contentMode: .fill)
|
||
} placeholder: {
|
||
Circle()
|
||
.fill(Color.gray.opacity(0.3))
|
||
.overlay(
|
||
Text(String(viewModel.moment.nick.prefix(1)))
|
||
.font(.system(size: 16, weight: .medium))
|
||
.foregroundColor(.white)
|
||
)
|
||
}
|
||
.frame(width: 44, height: 44)
|
||
.clipShape(Circle())
|
||
|
||
VStack(alignment: .leading, spacing: 2) {
|
||
Text(viewModel.moment.nick)
|
||
.font(.system(size: 16, weight: .medium))
|
||
.foregroundColor(.white)
|
||
UserIDDisplay(uid: viewModel.moment.uid, fontSize: 12, textColor: .white.opacity(0.6))
|
||
}
|
||
|
||
Spacer()
|
||
|
||
// 时间
|
||
Text(formatDisplayTime(viewModel.moment.publishTime))
|
||
.font(.system(size: 12, weight: .bold))
|
||
.foregroundColor(.white.opacity(0.8))
|
||
.padding(.horizontal, 6)
|
||
.padding(.vertical, 2)
|
||
.background(Color.white.opacity(0.15))
|
||
.cornerRadius(4)
|
||
}
|
||
|
||
// 动态内容
|
||
if !viewModel.moment.content.isEmpty {
|
||
Text(viewModel.moment.content)
|
||
.font(.system(size: 16))
|
||
.foregroundColor(.white.opacity(0.95))
|
||
.multilineTextAlignment(.leading)
|
||
}
|
||
|
||
// 图片网格
|
||
if let images = viewModel.moment.dynamicResList, !images.isEmpty {
|
||
MomentImageGrid(
|
||
images: images,
|
||
onImageTap: { images, index in
|
||
viewModel.onImageTap(index)
|
||
}
|
||
)
|
||
}
|
||
|
||
// 互动按钮
|
||
HStack(spacing: 20) {
|
||
Button {
|
||
viewModel.like()
|
||
} label: {
|
||
HStack(spacing: 6) {
|
||
if viewModel.isLikeLoading {
|
||
ProgressView()
|
||
.progressViewStyle(CircularProgressViewStyle(tint: viewModel.localIsLike ? .red : .white.opacity(0.8)))
|
||
.scaleEffect(0.8)
|
||
} else {
|
||
Image(systemName: viewModel.localIsLike ? "heart.fill" : "heart")
|
||
.font(.system(size: 18))
|
||
}
|
||
Text("\(viewModel.localLikeCount)")
|
||
.font(.system(size: 16))
|
||
}
|
||
}
|
||
.foregroundColor(viewModel.localIsLike ? .red : .white.opacity(0.9))
|
||
.disabled(viewModel.isLikeLoading || viewModel.moment.status == 0)
|
||
.opacity(viewModel.moment.status == 0 ? 0.5 : 1.0)
|
||
|
||
Spacer()
|
||
|
||
// 审核中状态角标 - 与外部列表保持一致:右侧对齐并与点赞按钮居中对齐
|
||
if viewModel.moment.status == 0 {
|
||
Text("reviewing")
|
||
.font(.system(size: 12, weight: .semibold))
|
||
.foregroundColor(.white)
|
||
.padding(.horizontal, 10)
|
||
.padding(.vertical, 6)
|
||
.background(Color.orange.opacity(0.85))
|
||
.clipShape(Capsule())
|
||
}
|
||
}
|
||
.padding(.top, 8)
|
||
}
|
||
.padding(.horizontal, 16)
|
||
.padding(.bottom, 16)
|
||
.safeAreaPadding(.top, 8)
|
||
}
|
||
}
|
||
|
||
}
|
||
.navigationBarHidden(true)
|
||
.fullScreenCover(isPresented: $viewModel.showImagePreview) {
|
||
ImagePreviewPager(
|
||
images: viewModel.images,
|
||
currentIndex: $viewModel.currentIndex
|
||
) {
|
||
viewModel.showImagePreview = false
|
||
}
|
||
}
|
||
.onAppear {
|
||
debugInfoSync("📱 MomentDetailPage: 显示详情页")
|
||
debugInfoSync(" 动态ID: \(viewModel.moment.dynamicId)")
|
||
debugInfoSync(" 用户: \(viewModel.moment.nick)")
|
||
debugInfoSync(" 审核状态: \(viewModel.moment.status)")
|
||
}
|
||
}
|
||
|
||
// MARK: - 时间显示逻辑
|
||
private func formatDisplayTime(_ timestamp: Int) -> String {
|
||
let date = Date(timeIntervalSince1970: TimeInterval(timestamp) / 1000.0)
|
||
let formatter = DateFormatter()
|
||
formatter.locale = Locale(identifier: "zh_CN")
|
||
let now = Date()
|
||
let interval = now.timeIntervalSince(date)
|
||
let calendar = Calendar.current
|
||
if calendar.isDateInToday(date) {
|
||
if interval < 60 {
|
||
return "刚刚"
|
||
} else if interval < 3600 {
|
||
return "\(Int(interval / 60))分钟前"
|
||
} else {
|
||
return "\(Int(interval / 3600))小时前"
|
||
}
|
||
} else {
|
||
formatter.dateFormat = "MM/dd"
|
||
return formatter.string(from: date)
|
||
}
|
||
}
|
||
}
|
||
|
||
//#Preview {
|
||
// let testMoment = MomentsInfo(
|
||
// dynamicId: 1,
|
||
// uid: 123456,
|
||
// nick: "测试用户",
|
||
// avatar: "",
|
||
// type: 0,
|
||
// content: "这是一条测试动态内容,用来测试 MomentDetailPage 的显示效果。",
|
||
// likeCount: 42,
|
||
// isLike: false,
|
||
// commentCount: 5,
|
||
// publishTime: Int(Date().timeIntervalSince1970 * 1000),
|
||
// worldId: 1,
|
||
// status: 0, // 审核中状态
|
||
// playCount: nil,
|
||
// dynamicResList: [
|
||
// MomentsPicture(id: 1, resUrl: "https://picsum.photos/300/300", format: "jpg", width: 300, height: 300, resDuration: nil),
|
||
// MomentsPicture(id: 2, resUrl: "https://picsum.photos/301/301", format: "jpg", width: 301, height: 301, resDuration: nil)
|
||
// ],
|
||
// gender: nil,
|
||
// squareTop: nil,
|
||
// topicTop: nil,
|
||
// newUser: nil,
|
||
// defUser: nil,
|
||
// scene: nil,
|
||
// userVipInfoVO: nil,
|
||
// headwearPic: nil,
|
||
// headwearEffect: nil,
|
||
// headwearType: nil,
|
||
// headwearName: nil,
|
||
// headwearId: nil,
|
||
// experLevelPic: nil,
|
||
// charmLevelPic: nil,
|
||
// isCustomWord: nil,
|
||
// labelList: nil
|
||
// )
|
||
//
|
||
// MomentDetailPage(moment: testMoment) {
|
||
// print("关闭详情页")
|
||
// }
|
||
//}
|