
- 在MePage和MomentListHomePage中新增动态点击事件,支持打开动态详情页。 - 创建MomentDetailPage视图,展示动态详细信息,包括用户信息、动态内容和互动按钮。 - 实现MomentDetailViewModel,管理动态详情页的状态和点赞逻辑。 - 更新MomentListItem组件,添加整体点击回调,提升用户交互体验。 - 优化背景视图组件,确保一致的视觉效果。
438 lines
12 KiB
Swift
438 lines
12 KiB
Swift
import Foundation
|
||
|
||
// MARK: - 响应数据模型
|
||
|
||
/// 最新动态响应结构
|
||
struct MomentsLatestResponse: Codable, Equatable, Sendable {
|
||
let code: Int
|
||
let message: String
|
||
let data: MomentsListData?
|
||
let timestamp: Int?
|
||
}
|
||
|
||
/// 动态列表数据
|
||
struct MomentsListData: Codable, Equatable, Sendable {
|
||
let dynamicList: [MomentsInfo]
|
||
let nextDynamicId: Int
|
||
}
|
||
|
||
/// 动态信息结构
|
||
public struct MomentsInfo: Codable, Equatable, Sendable, Identifiable {
|
||
let dynamicId: Int
|
||
let uid: Int
|
||
let nick: String
|
||
let avatar: String
|
||
let type: Int
|
||
let content: String
|
||
let likeCount: Int
|
||
let isLike: Bool
|
||
let commentCount: Int
|
||
let publishTime: Int
|
||
let worldId: Int
|
||
let status: Int
|
||
// data.md 里部分字段可选
|
||
let playCount: Int?
|
||
let dynamicResList: [MomentsPicture]?
|
||
// 以下字段后端未返回,全部可选
|
||
let gender: Int?
|
||
let squareTop: Int?
|
||
let topicTop: Int?
|
||
let newUser: Bool?
|
||
let defUser: Int?
|
||
let scene: String?
|
||
let userVipInfoVO: UserVipInfo?
|
||
let headwearPic: String?
|
||
let headwearEffect: String?
|
||
let headwearType: Int?
|
||
let headwearName: String?
|
||
let headwearId: Int?
|
||
let experLevelPic: String?
|
||
let charmLevelPic: String?
|
||
let isCustomWord: Bool?
|
||
let labelList: [String]?
|
||
// 计算属性
|
||
public var id: Int { dynamicId } // Identifiable 协议要求
|
||
var isSquareTop: Bool { (squareTop ?? 0) != 0 }
|
||
var isTopicTop: Bool { (topicTop ?? 0) != 0 }
|
||
var formattedPublishTime: Date {
|
||
Date(timeIntervalSince1970: TimeInterval(publishTime) / 1000.0)
|
||
}
|
||
}
|
||
|
||
/// 动态图片信息
|
||
struct MomentsPicture: Codable, Equatable, Sendable {
|
||
let id: Int?
|
||
let resUrl: String?
|
||
let format: String?
|
||
let width: Int?
|
||
let height: Int?
|
||
let resDuration: Int? // 可选字段,因为有些图片没有这个字段
|
||
}
|
||
|
||
/// 用户VIP信息 - 完整版本,所有字段都是可选的
|
||
struct UserVipInfo: Codable, Equatable, Sendable {
|
||
let vipLevel: Int?
|
||
let vipName: String?
|
||
let vipIcon: String?
|
||
let vipLogo: String?
|
||
let nameplateId: Int?
|
||
let nameplateUrl: String?
|
||
let userCardBG: String?
|
||
let expireTime: Int?
|
||
let preventKick: Bool?
|
||
let preventTrace: Bool?
|
||
let preventFollow: Bool?
|
||
let micNickColour: String?
|
||
let micCircle: String?
|
||
let enterRoomEffects: String?
|
||
let medalSeat: Int?
|
||
let friendNickColour: String?
|
||
let visitHide: Bool?
|
||
let visitListView: Bool?
|
||
let privateChatLimit: Bool?
|
||
let roomPicScreen: Bool?
|
||
let uploadGifAvatar: Bool?
|
||
let enterHide: Bool?
|
||
}
|
||
|
||
// MARK: - 内容类型枚举
|
||
|
||
/// 动态内容类型
|
||
enum MomentsContentType: Int, CaseIterable {
|
||
case text = 0 // 纯文字
|
||
case picture = 2 // 图片
|
||
|
||
/// 转换为API参数字符串
|
||
static func toAPIParameter(_ types: [MomentsContentType]) -> String {
|
||
return types.map { String($0.rawValue) }.joined(separator: ",")
|
||
}
|
||
}
|
||
|
||
// MARK: - 最新动态 API 请求
|
||
|
||
/// 获取最新动态列表的API请求
|
||
struct LatestDynamicsRequest: APIRequestProtocol {
|
||
typealias Response = MomentsLatestResponse
|
||
|
||
let endpoint: String = APIEndpoint.latestDynamics.path
|
||
let method: HTTPMethod = .GET
|
||
|
||
let dynamicId: String
|
||
let pageSize: Int
|
||
let types: [MomentsContentType]
|
||
|
||
/// 初始化请求
|
||
/// - Parameters:
|
||
/// - dynamicId: 最新动态的ID,用于分页加载。首次请求传空字符串
|
||
/// - pageSize: 每页返回的数据数量,默认20
|
||
/// - types: 动态内容类型数组,默认包含文字和图片
|
||
init(
|
||
dynamicId: String = "",
|
||
pageSize: Int = 20,
|
||
types: [MomentsContentType] = [.text, .picture]
|
||
) {
|
||
self.dynamicId = dynamicId
|
||
self.pageSize = pageSize
|
||
self.types = types
|
||
}
|
||
|
||
var queryParameters: [String: String]? {
|
||
return [
|
||
"dynamicId": dynamicId,
|
||
"pageSize": String(pageSize),
|
||
"types": MomentsContentType.toAPIParameter(types)
|
||
]
|
||
}
|
||
|
||
var bodyParameters: [String: Any]? { nil }
|
||
|
||
var includeBaseParameters: Bool { true }
|
||
|
||
// Loading 配置
|
||
var shouldShowLoading: Bool { true }
|
||
var shouldShowError: Bool { true }
|
||
}
|
||
|
||
// MARK: - 发布动态 API 请求与响应
|
||
|
||
/// 动态图片资源信息
|
||
struct ResListItem: Codable, Equatable {
|
||
let resUrl: String
|
||
let width: Int
|
||
let height: Int
|
||
let format: String
|
||
}
|
||
|
||
/// 发布动态请求
|
||
struct PublishFeedRequest: APIRequestProtocol {
|
||
typealias Response = PublishFeedResponse
|
||
|
||
let endpoint: String = APIEndpoint.publishFeed.path
|
||
let method: HTTPMethod = .POST
|
||
|
||
let content: String
|
||
let uid: String
|
||
let type: String
|
||
var pub_sign: String
|
||
let resList: [ResListItem]?
|
||
|
||
var queryParameters: [String: String]? { nil }
|
||
var bodyParameters: [String: Any]? {
|
||
var params: [String: Any] = [
|
||
"content": content,
|
||
"uid": uid,
|
||
"type": type,
|
||
"pub_sign": pub_sign
|
||
]
|
||
if let resList = resList, !resList.isEmpty {
|
||
params["resList"] = resList.map { [
|
||
"resUrl": $0.resUrl,
|
||
"width": $0.width,
|
||
"height": $0.height,
|
||
"format": $0.format
|
||
] }
|
||
}
|
||
return params
|
||
}
|
||
var includeBaseParameters: Bool { true }
|
||
var shouldShowLoading: Bool { true }
|
||
var shouldShowError: Bool { true }
|
||
|
||
/// async 工厂方法,主线程生成 pub_sign
|
||
static func make(content: String, uid: String, type: String = "0", resList: [ResListItem]? = nil) async -> PublishFeedRequest {
|
||
let base = await MainActor.run { BaseRequest() }
|
||
var mutableBase = base
|
||
mutableBase.generateSignature(with: [
|
||
"content": content,
|
||
"uid": uid,
|
||
"type": type
|
||
])
|
||
return PublishFeedRequest(
|
||
content: content,
|
||
uid: uid,
|
||
type: type,
|
||
pub_sign: mutableBase.pubSign,
|
||
resList: resList
|
||
)
|
||
}
|
||
|
||
/// 禁止外部直接调用
|
||
private init(content: String, uid: String, type: String, pub_sign: String, resList: [ResListItem]?) {
|
||
self.content = content
|
||
self.uid = uid
|
||
self.type = type
|
||
self.pub_sign = pub_sign
|
||
self.resList = resList
|
||
}
|
||
}
|
||
|
||
/// 发布动态响应
|
||
struct PublishFeedResponse: Codable, Equatable {
|
||
let code: Int
|
||
let message: String
|
||
let data: PublishFeedData?
|
||
let timestamp: Int?
|
||
}
|
||
|
||
/// 发布动态返回数据
|
||
struct PublishFeedData: Codable, Equatable {
|
||
let dynamicId: Int?
|
||
}
|
||
|
||
// MARK: - 我的动态 API 请求
|
||
|
||
/// 我的动态信息结构 - 专门用于 /dynamic/getMyDynamic 接口
|
||
struct MyMomentInfo: Codable, Equatable, Sendable {
|
||
// 服务器可能返回的完整字段(均用可选兼容不同版本)
|
||
let dynamicId: Int?
|
||
let uid: Int
|
||
let nick: String?
|
||
let avatar: String?
|
||
let type: Int
|
||
let content: String
|
||
let likeCount: Int?
|
||
let isLike: Bool?
|
||
let commentCount: Int?
|
||
let publishTime: Int64
|
||
let worldId: Int?
|
||
let status: Int?
|
||
let playCount: Int?
|
||
let dynamicResList: [MomentsPicture]? // 资源列表(图片/视频)
|
||
|
||
// 转换为 MomentsInfo 的辅助方法
|
||
func toMomentsInfo() -> MomentsInfo {
|
||
return MomentsInfo(
|
||
dynamicId: dynamicId ?? 0,
|
||
uid: uid,
|
||
nick: nick ?? "",
|
||
avatar: avatar ?? "",
|
||
type: type,
|
||
content: content,
|
||
likeCount: likeCount ?? 0,
|
||
isLike: isLike ?? false,
|
||
commentCount: commentCount ?? 0,
|
||
// 注意:UI 的 formatDisplayTime 期望毫秒,这里不做 /1000 转换
|
||
publishTime: Int(publishTime),
|
||
worldId: worldId ?? 0,
|
||
status: status ?? 1,
|
||
playCount: playCount,
|
||
dynamicResList: dynamicResList,
|
||
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
|
||
)
|
||
}
|
||
}
|
||
|
||
/// 我的动态响应结构
|
||
struct MyMomentsResponse: Codable, Equatable, Sendable {
|
||
let code: Int
|
||
let message: String
|
||
let data: [MyMomentInfo]?
|
||
let timestamp: Int64?
|
||
}
|
||
|
||
struct GetMyDynamicRequest: APIRequestProtocol {
|
||
typealias Response = MyMomentsResponse
|
||
|
||
let endpoint: String = APIEndpoint.getMyDynamic.path
|
||
let method: HTTPMethod = .POST
|
||
|
||
let fromUid: Int
|
||
let uid: Int
|
||
let page: Int
|
||
let pageSize: Int
|
||
|
||
init(fromUid: Int, uid: Int, page: Int = 1, pageSize: Int = 20) {
|
||
self.fromUid = fromUid
|
||
self.uid = uid
|
||
self.page = page
|
||
self.pageSize = pageSize
|
||
}
|
||
|
||
var queryParameters: [String: String]? {
|
||
[
|
||
"fromUid": String(fromUid),
|
||
"uid": String(uid),
|
||
"page": String(page),
|
||
"pageSize": String(pageSize)
|
||
]
|
||
}
|
||
|
||
var bodyParameters: [String: Any]? { nil }
|
||
var includeBaseParameters: Bool { true }
|
||
var shouldShowLoading: Bool { true }
|
||
var shouldShowError: Bool { true }
|
||
}
|
||
|
||
// MARK: - 动态点赞 API 请求与响应
|
||
|
||
/// 动态点赞响应结构
|
||
struct LikeDynamicResponse: Codable, Equatable, Sendable {
|
||
let code: Int
|
||
let message: String
|
||
let data: LikeDynamicData?
|
||
let timestamp: Int?
|
||
}
|
||
|
||
/// 动态点赞返回数据
|
||
struct LikeDynamicData: Codable, Equatable, Sendable {
|
||
let success: Bool?
|
||
let likeCount: Int?
|
||
}
|
||
|
||
/// 动态点赞请求
|
||
struct LikeDynamicRequest: APIRequestProtocol {
|
||
typealias Response = LikeDynamicResponse
|
||
|
||
let endpoint: String = APIEndpoint.dynamicLike.path
|
||
let method: HTTPMethod = .POST
|
||
|
||
let dynamicId: Int
|
||
let uid: Int
|
||
let status: Int // 0: 取消点赞, 1: 点赞
|
||
let likedUid: Int
|
||
let worldId: Int
|
||
|
||
init(dynamicId: Int, uid: Int, status: Int, likedUid: Int, worldId: Int) {
|
||
self.dynamicId = dynamicId
|
||
self.uid = uid
|
||
self.status = status
|
||
self.likedUid = likedUid
|
||
self.worldId = worldId
|
||
}
|
||
|
||
var bodyParameters: [String: Any]? { nil }
|
||
|
||
var queryParameters: [String: String]? {
|
||
return [
|
||
"dynamicId": String(dynamicId),
|
||
"uid": String(uid),
|
||
"status": String(status),
|
||
"likedUid": String(likedUid),
|
||
"worldId": String(worldId)
|
||
]
|
||
}
|
||
|
||
var includeBaseParameters: Bool { true }
|
||
var shouldShowLoading: Bool { true }
|
||
var shouldShowError: Bool { true }
|
||
}
|
||
|
||
// MARK: - 删除动态 API 请求与响应
|
||
|
||
/// 删除动态响应结构
|
||
struct DeleteDynamicResponse: Codable, Equatable, Sendable {
|
||
let code: Int
|
||
let message: String
|
||
let data: DeleteDynamicData?
|
||
let timestamp: Int?
|
||
}
|
||
|
||
/// 删除动态返回数据
|
||
struct DeleteDynamicData: Codable, Equatable, Sendable {
|
||
let success: Bool?
|
||
}
|
||
|
||
/// 删除动态请求
|
||
struct DeleteDynamicRequest: APIRequestProtocol {
|
||
typealias Response = DeleteDynamicResponse
|
||
|
||
let endpoint: String = APIEndpoint.deleteDynamic.path
|
||
let method: HTTPMethod = .POST
|
||
|
||
let dynamicId: Int
|
||
let uid: Int
|
||
|
||
init(dynamicId: Int, uid: Int) {
|
||
self.dynamicId = dynamicId
|
||
self.uid = uid
|
||
}
|
||
|
||
var queryParameters: [String: String]? { nil }
|
||
|
||
var bodyParameters: [String: Any]? {
|
||
return [
|
||
"dynamicId": dynamicId,
|
||
"uid": uid
|
||
]
|
||
}
|
||
|
||
var includeBaseParameters: Bool { true }
|
||
var shouldShowLoading: Bool { true }
|
||
var shouldShowError: Bool { true }
|
||
}
|