feat: add live module and optimize report list
This commit is contained in:
370
suixinkan/Features/Live/Models/LiveModels.swift
Normal file
370
suixinkan/Features/Live/Models/LiveModels.swift
Normal file
@ -0,0 +1,370 @@
|
||||
//
|
||||
// LiveModels.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 直播状态,对齐 Android `AliveEntity.status`。
|
||||
enum LiveStatus: Int, Sendable, Equatable {
|
||||
case pending = 1
|
||||
case living = 2
|
||||
case paused = 3
|
||||
case finished = 4
|
||||
case unknown = 0
|
||||
|
||||
/// 是否允许开始、暂停或结束。
|
||||
var isOperable: Bool { self != .finished }
|
||||
}
|
||||
|
||||
/// 直播推流模式,对齐 Android `manualPushMode`。
|
||||
enum LivePushMode: Int, CaseIterable, Sendable, Equatable {
|
||||
case clarityFirst = 1
|
||||
case smoothFirst = 2
|
||||
|
||||
/// 展示文案。
|
||||
var title: String {
|
||||
switch self {
|
||||
case .clarityFirst: "清晰度优先"
|
||||
case .smoothFirst: "流畅度优先"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 直播列表与详情实体,对齐 Android `AliveEntity`。
|
||||
struct LiveItem: Decodable, Sendable, Equatable, Hashable, Identifiable {
|
||||
let id: Int
|
||||
let title: String
|
||||
let coverImg: String
|
||||
let pushUrl: String
|
||||
let startTime: Int64
|
||||
let endTime: Int64
|
||||
let duration: Int64
|
||||
let status: Int
|
||||
let statusLabel: String
|
||||
let manualPushMode: Int
|
||||
let manualPushState: Int
|
||||
let viewsCount: Int
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case title
|
||||
case coverImg = "cover_img"
|
||||
case pushUrl = "push_url"
|
||||
case startTime = "start_time"
|
||||
case endTime = "end_time"
|
||||
case duration
|
||||
case status
|
||||
case statusLabel = "status_label"
|
||||
case manualPushMode = "manual_push_mode"
|
||||
case manualPushState = "manual_push_state"
|
||||
case legacyManualPushMode = "manaul_push_mode"
|
||||
case legacyManualPushState = "manaul_push_state"
|
||||
case viewsCount = "views_count"
|
||||
}
|
||||
|
||||
init(
|
||||
id: Int = 0,
|
||||
title: String = "",
|
||||
coverImg: String = "",
|
||||
pushUrl: String = "",
|
||||
startTime: Int64 = 0,
|
||||
endTime: Int64 = 0,
|
||||
duration: Int64 = 0,
|
||||
status: Int = 0,
|
||||
statusLabel: String = "",
|
||||
manualPushMode: Int = 1,
|
||||
manualPushState: Int = 0,
|
||||
viewsCount: Int = 0
|
||||
) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.coverImg = coverImg
|
||||
self.pushUrl = pushUrl
|
||||
self.startTime = startTime
|
||||
self.endTime = endTime
|
||||
self.duration = duration
|
||||
self.status = status
|
||||
self.statusLabel = statusLabel
|
||||
self.manualPushMode = manualPushMode
|
||||
self.manualPushState = manualPushState
|
||||
self.viewsCount = viewsCount
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try container.decodeLossyInt(forKey: .id) ?? 0
|
||||
title = try container.decodeLossyString(forKey: .title)
|
||||
coverImg = try container.decodeLossyString(forKey: .coverImg).unescapedSlashes
|
||||
pushUrl = try container.decodeLossyString(forKey: .pushUrl).unescapedSlashes
|
||||
startTime = try container.decodeLossyInt64(forKey: .startTime) ?? 0
|
||||
endTime = try container.decodeLossyInt64(forKey: .endTime) ?? 0
|
||||
duration = try container.decodeLossyInt64(forKey: .duration) ?? 0
|
||||
status = try container.decodeLossyInt(forKey: .status) ?? 0
|
||||
statusLabel = try container.decodeLossyString(forKey: .statusLabel)
|
||||
manualPushMode = try container.decodeLossyInt(forKey: .manualPushMode)
|
||||
?? container.decodeLossyInt(forKey: .legacyManualPushMode)
|
||||
?? 1
|
||||
manualPushState = try container.decodeLossyInt(forKey: .manualPushState)
|
||||
?? container.decodeLossyInt(forKey: .legacyManualPushState)
|
||||
?? 0
|
||||
viewsCount = try container.decodeLossyInt(forKey: .viewsCount) ?? 0
|
||||
}
|
||||
|
||||
/// 类型化直播状态。
|
||||
var liveStatus: LiveStatus { LiveStatus(rawValue: status) ?? .unknown }
|
||||
|
||||
/// 标题兜底。
|
||||
var displayTitle: String { title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? "暂无标题" : title }
|
||||
|
||||
/// 当前操作按钮标题。
|
||||
var controlTitle: String { liveStatus == .living ? "暂停" : "开始" }
|
||||
}
|
||||
|
||||
/// 直播分页响应,对齐 Android `ItemListResponse<AliveEntity>`。
|
||||
struct LiveListResponse: Decodable, Sendable, Equatable {
|
||||
let page: Int
|
||||
let pageSize: Int
|
||||
let total: Int
|
||||
let totalPages: Int
|
||||
let items: [LiveItem]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case page
|
||||
case pageSize = "page_size"
|
||||
case total
|
||||
case totalPages = "total_pages"
|
||||
case items
|
||||
}
|
||||
|
||||
init(page: Int = 1, pageSize: Int = 10, total: Int = 0, totalPages: Int = 0, items: [LiveItem] = []) {
|
||||
self.page = page
|
||||
self.pageSize = pageSize
|
||||
self.total = total
|
||||
self.totalPages = totalPages
|
||||
self.items = items
|
||||
}
|
||||
}
|
||||
|
||||
/// 新增直播请求。
|
||||
struct LiveCreateRequest: Encodable, Sendable, Equatable {
|
||||
let scenicId: String
|
||||
let title: String
|
||||
let coverImg: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case scenicId = "scenic_id"
|
||||
case title
|
||||
case coverImg = "cover_img"
|
||||
}
|
||||
}
|
||||
|
||||
/// 直播控制请求。
|
||||
struct LiveControlRequest: Encodable, Sendable, Equatable {
|
||||
let liveId: Int
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case liveId = "live_id"
|
||||
}
|
||||
}
|
||||
|
||||
/// 直播推流模式请求。
|
||||
struct LivePushModeRequest: Encodable, Sendable, Equatable {
|
||||
let liveId: Int
|
||||
let manualPushMode: Int
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case liveId = "live_id"
|
||||
case manualPushMode = "manual_push_mode"
|
||||
}
|
||||
}
|
||||
|
||||
/// 直播相册文件实体,对齐 Android `AliveAlbumFileEntity`。
|
||||
struct LiveAlbumFile: Codable, Sendable, Equatable, Hashable, Identifiable {
|
||||
let url: String
|
||||
let type: Int
|
||||
let size: Int64
|
||||
let coverImg: String?
|
||||
let id: Int
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case url
|
||||
case type
|
||||
case size
|
||||
case coverImg = "cover_img"
|
||||
case id
|
||||
}
|
||||
|
||||
init(url: String, type: Int, size: Int64, coverImg: String? = nil, id: Int = 0) {
|
||||
self.url = url.unescapedSlashes
|
||||
self.type = type
|
||||
self.size = size
|
||||
self.coverImg = coverImg?.unescapedSlashes
|
||||
self.id = id
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
url = try container.decodeLossyString(forKey: .url).unescapedSlashes
|
||||
type = try container.decodeLossyInt(forKey: .type) ?? 2
|
||||
size = try container.decodeLossyInt64(forKey: .size) ?? 0
|
||||
let cover = try container.decodeIfPresent(String.self, forKey: .coverImg)
|
||||
coverImg = cover?.unescapedSlashes
|
||||
id = try container.decodeLossyInt(forKey: .id) ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
/// 直播相册文件夹实体,对齐 Android `AliveAlbumFolderEntity`。
|
||||
struct LiveAlbumFolder: Decodable, Sendable, Equatable, Hashable, Identifiable {
|
||||
let id: Int
|
||||
let albumId: Int
|
||||
let name: String
|
||||
let creator: String
|
||||
let items: [LiveAlbumFile]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case albumId = "album_id"
|
||||
case name
|
||||
case creator
|
||||
case items
|
||||
}
|
||||
|
||||
init(id: Int = 0, albumId: Int = 0, name: String = "", creator: String = "", items: [LiveAlbumFile] = []) {
|
||||
self.id = id
|
||||
self.albumId = albumId
|
||||
self.name = name
|
||||
self.creator = creator
|
||||
self.items = items
|
||||
}
|
||||
|
||||
/// 标题兜底。
|
||||
var displayName: String { name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? "暂无标题" : name }
|
||||
}
|
||||
|
||||
/// 直播相册列表响应。
|
||||
struct LiveAlbumFolderListResponse: Decodable, Sendable, Equatable {
|
||||
let items: [LiveAlbumFolder]
|
||||
let page: Int
|
||||
let pageSize: Int
|
||||
let total: Int
|
||||
let totalPages: Int
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case items
|
||||
case page
|
||||
case pageSize = "page_size"
|
||||
case total
|
||||
case totalPages = "total_pages"
|
||||
}
|
||||
|
||||
init(items: [LiveAlbumFolder] = [], page: Int = 1, pageSize: Int = 10, total: Int = 0, totalPages: Int = 0) {
|
||||
self.items = items
|
||||
self.page = page
|
||||
self.pageSize = pageSize
|
||||
self.total = total
|
||||
self.totalPages = totalPages
|
||||
}
|
||||
}
|
||||
|
||||
/// 创建直播相册文件夹请求。
|
||||
struct LiveAlbumFolderCreateRequest: Encodable, Sendable, Equatable {
|
||||
let scenicId: String
|
||||
let name: String
|
||||
let items: [LiveAlbumFile]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case scenicId = "scenic_id"
|
||||
case name
|
||||
case items
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除直播相册文件夹请求。
|
||||
struct LiveAlbumFolderDeleteRequest: Encodable, Sendable, Equatable {
|
||||
let folderId: Int
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case folderId = "folder_id"
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除直播相册文件请求。
|
||||
struct LiveAlbumFileDeleteRequest: Encodable, Sendable, Equatable {
|
||||
let folderId: Int
|
||||
let fileIds: [Int]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case folderId = "folder_id"
|
||||
case fileIds = "file_ids"
|
||||
}
|
||||
}
|
||||
|
||||
/// 本地待上传直播相册素材。
|
||||
struct LivePickedMedia: Sendable, Equatable {
|
||||
let data: Data
|
||||
let fileName: String
|
||||
let size: Int64
|
||||
let width: Int
|
||||
let height: Int
|
||||
}
|
||||
|
||||
private extension KeyedDecodingContainer {
|
||||
func decodeLossyString(forKey key: Key) throws -> String {
|
||||
if let value = try? decodeIfPresent(String.self, forKey: key) {
|
||||
return value
|
||||
}
|
||||
if let value = try? decodeIfPresent(Int.self, forKey: key) {
|
||||
return String(value)
|
||||
}
|
||||
if let value = try? decodeIfPresent(Int64.self, forKey: key) {
|
||||
return String(value)
|
||||
}
|
||||
if let value = try? decodeIfPresent(Double.self, forKey: key) {
|
||||
return String(value)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func decodeLossyInt(forKey key: Key) throws -> Int? {
|
||||
if let value = try? decodeIfPresent(Int.self, forKey: key) {
|
||||
return value
|
||||
}
|
||||
if let value = try? decodeIfPresent(Int64.self, forKey: key) {
|
||||
return Int(value)
|
||||
}
|
||||
if let value = try? decodeIfPresent(String.self, forKey: key) {
|
||||
let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if let intValue = Int(text) { return intValue }
|
||||
if let doubleValue = Double(text) { return Int(doubleValue) }
|
||||
}
|
||||
if let value = try? decodeIfPresent(Double.self, forKey: key) {
|
||||
return Int(value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeLossyInt64(forKey key: Key) throws -> Int64? {
|
||||
if let value = try? decodeIfPresent(Int64.self, forKey: key) {
|
||||
return value
|
||||
}
|
||||
if let value = try? decodeIfPresent(Int.self, forKey: key) {
|
||||
return Int64(value)
|
||||
}
|
||||
if let value = try? decodeIfPresent(String.self, forKey: key) {
|
||||
let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if let intValue = Int64(text) { return intValue }
|
||||
if let doubleValue = Double(text) { return Int64(doubleValue) }
|
||||
}
|
||||
if let value = try? decodeIfPresent(Double.self, forKey: key) {
|
||||
return Int64(value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private extension String {
|
||||
var unescapedSlashes: String {
|
||||
replacingOccurrences(of: "\\/", with: "/")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user