feat: add live module and optimize report list

This commit is contained in:
2026-07-08 14:25:14 +08:00
parent 8ca8bcf948
commit 4a78a0c21a
14 changed files with 3712 additions and 102 deletions

View 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: "/")
}
}