Initial commit: suixinkan_ios UIKit rewrite project.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-26 14:33:31 +08:00
commit 9edf993432
297 changed files with 47151 additions and 0 deletions

View File

@ -0,0 +1,415 @@
//
// LiveModels.swift
// suixinkan
//
// Created by Codex on 2026/6/25.
//
import Foundation
///
struct LiveListResponse: Decodable {
let items: [LiveEntity]
let total: Int
let page: Int
let pageSize: Int
enum CodingKeys: String, CodingKey {
case items
case total
case page
case pageSize = "page_size"
}
init(items: [LiveEntity] = [], total: Int = 0, page: Int = 1, pageSize: Int = 10) {
self.items = items
self.total = total
self.page = page
self.pageSize = pageSize
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
items = (try? container.decode([LiveEntity].self, forKey: .items)) ?? []
total = try container.liveDecodeLossyInt(forKey: .total) ?? 0
page = try container.liveDecodeLossyInt(forKey: .page) ?? 1
pageSize = try container.liveDecodeLossyInt(forKey: .pageSize) ?? 10
}
}
///
struct LiveEntity: Decodable, Identifiable, Equatable {
let id: Int
let title: String
let coverImg: String
let pushUrl: String
let playUrl: String
let pullUrl: String
let hlsUrl: String
let flvUrl: String
let liveUrl: 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 playUrl = "play_url"
case pullUrl = "pull_url"
case hlsUrl = "hls_url"
case flvUrl = "flv_url"
case liveUrl = "live_url"
case startTime = "start_time"
case endTime = "end_time"
case duration
case status
case statusLabel = "status_label"
case manualPushMode = "manaul_push_mode"
case manualPushState = "manaul_push_state"
case viewsCount = "views_count"
}
init(
id: Int = 0,
title: String = "",
coverImg: String = "",
pushUrl: String = "",
playUrl: String = "",
pullUrl: String = "",
hlsUrl: String = "",
flvUrl: String = "",
liveUrl: 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.playUrl = playUrl
self.pullUrl = pullUrl
self.hlsUrl = hlsUrl
self.flvUrl = flvUrl
self.liveUrl = liveUrl
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.liveDecodeLossyInt(forKey: .id) ?? 0
title = try container.liveDecodeLossyString(forKey: .title)
coverImg = try container.liveDecodeLossyString(forKey: .coverImg)
pushUrl = try container.liveDecodeLossyString(forKey: .pushUrl)
playUrl = try container.liveDecodeLossyString(forKey: .playUrl)
pullUrl = try container.liveDecodeLossyString(forKey: .pullUrl)
hlsUrl = try container.liveDecodeLossyString(forKey: .hlsUrl)
flvUrl = try container.liveDecodeLossyString(forKey: .flvUrl)
liveUrl = try container.liveDecodeLossyString(forKey: .liveUrl)
startTime = Int64(try container.liveDecodeLossyInt(forKey: .startTime) ?? 0)
endTime = Int64(try container.liveDecodeLossyInt(forKey: .endTime) ?? 0)
duration = Int64(try container.liveDecodeLossyInt(forKey: .duration) ?? 0)
status = try container.liveDecodeLossyInt(forKey: .status) ?? 0
statusLabel = try container.liveDecodeLossyString(forKey: .statusLabel)
manualPushMode = try container.liveDecodeLossyInt(forKey: .manualPushMode) ?? 1
manualPushState = try container.liveDecodeLossyInt(forKey: .manualPushState) ?? 0
viewsCount = try container.liveDecodeLossyInt(forKey: .viewsCount) ?? 0
}
var displayStatus: String {
statusLabel.liveNonEmpty ?? "状态\(status)"
}
var playbackURLCandidates: [String] {
[playUrl, pullUrl, hlsUrl, liveUrl, flvUrl]
}
}
///
struct LiveCreateRequest: Encodable, 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, Equatable {
let liveId: Int
enum CodingKeys: String, CodingKey {
case liveId = "live_id"
}
}
///
struct LivePushModeRequest: Encodable, Equatable {
let liveId: Int
let manualPushMode: Int
enum CodingKeys: String, CodingKey {
case liveId = "live_id"
case manualPushMode = "manual_push_mode"
}
}
///
struct LiveAlbumFolderListResponse: Decodable {
let items: [LiveAlbumFolderItem]
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: [LiveAlbumFolderItem] = [], 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
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
items = (try? container.decode([LiveAlbumFolderItem].self, forKey: .items)) ?? []
page = try container.liveDecodeLossyInt(forKey: .page) ?? 1
pageSize = try container.liveDecodeLossyInt(forKey: .pageSize) ?? 10
total = try container.liveDecodeLossyInt(forKey: .total) ?? 0
totalPages = try container.liveDecodeLossyInt(forKey: .totalPages) ?? 0
}
}
///
struct LiveAlbumFolderItem: Decodable, Identifiable, Equatable {
let id: Int
let albumId: Int
let name: String
let creator: String
let items: [LiveAlbumFileItem]
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: [LiveAlbumFileItem] = []) {
self.id = id
self.albumId = albumId
self.name = name
self.creator = creator
self.items = items
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.liveDecodeLossyInt(forKey: .id) ?? 0
albumId = try container.liveDecodeLossyInt(forKey: .albumId) ?? 0
name = try container.liveDecodeLossyString(forKey: .name)
creator = try container.liveDecodeLossyString(forKey: .creator)
items = (try? container.decode([LiveAlbumFileItem].self, forKey: .items)) ?? []
}
}
///
struct LiveAlbumFileItem: Decodable, Identifiable, Hashable {
let id: Int
let url: String
let type: Int
let size: Int64
let coverImg: String?
enum CodingKeys: String, CodingKey {
case id
case url
case type
case size
case coverImg = "cover_img"
}
init(id: Int = 0, url: String = "", type: Int = 1, size: Int64 = 0, coverImg: String? = nil) {
self.id = id
self.url = url
self.type = type
self.size = size
self.coverImg = coverImg
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.liveDecodeLossyInt(forKey: .id) ?? 0
url = try container.liveDecodeLossyString(forKey: .url)
type = try container.liveDecodeLossyInt(forKey: .type) ?? 1
size = Int64(try container.liveDecodeLossyInt(forKey: .size) ?? 0)
coverImg = try? container.decodeIfPresent(String.self, forKey: .coverImg)
}
var isVideo: Bool { type == 2 }
var previewURL: String {
if let cover = coverImg?.liveTrimmed, !cover.isEmpty {
return cover
}
return url
}
}
///
struct LiveAlbumCreateFolderRequest: Encodable, Equatable {
let scenicId: String
let name: String
let items: [LiveAlbumCreateFileItem]
enum CodingKeys: String, CodingKey {
case scenicId = "scenic_id"
case name
case items
}
}
///
struct LiveAlbumCreateFileItem: Encodable, Equatable {
let url: String
let type: Int
let size: Int64
let coverImg: String?
enum CodingKeys: String, CodingKey {
case url
case type
case size
case coverImg = "cover_img"
}
}
///
struct LiveAlbumDeleteFolderRequest: Encodable, Equatable {
let folderId: Int
enum CodingKeys: String, CodingKey {
case folderId = "folder_id"
}
}
///
struct LiveAlbumDeleteFilesRequest: Encodable, Equatable {
let folderId: Int
let fileIds: [Int]
enum CodingKeys: String, CodingKey {
case folderId = "folder_id"
case fileIds = "file_ids"
}
}
///
struct LiveAlbumLocalUploadFile: Identifiable, Equatable {
let id: UUID
let data: Data
let fileName: String
let fileType: Int
let size: Int64
var uploadedURL: String?
init(id: UUID = UUID(), data: Data, fileName: String, fileType: Int, size: Int64? = nil, uploadedURL: String? = nil) {
self.id = id
self.data = data
self.fileName = fileName
self.fileType = fileType
self.size = size ?? Int64(data.count)
self.uploadedURL = uploadedURL
}
var isVideo: Bool { fileType == 2 }
}
private extension KeyedDecodingContainer {
func liveDecodeLossyString(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)
}
if let value = try? decodeIfPresent(Bool.self, forKey: key) {
return value ? "1" : "0"
}
return ""
}
func liveDecodeLossyInt(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(Double.self, forKey: key) {
return Int(value)
}
if let value = try? decodeIfPresent(String.self, forKey: key) {
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
if let intValue = Int(trimmed) {
return intValue
}
if let doubleValue = Double(trimmed) {
return Int(doubleValue)
}
}
return nil
}
}
extension String {
/// 使
var liveTrimmed: String {
trimmingCharacters(in: .whitespacesAndNewlines)
}
/// 使
var liveNonEmpty: String? {
let value = liveTrimmed
return value.isEmpty ? nil : value
}
}