Advance UIKit rewrite with AMap integration and core UI modules.
Integrate高德 SDK with simulator-safe build flags, add map views for operating area and punch points, refactor main tabs and key feature screens to UIKit with Diffable lists, and document Swift concurrency defaults in AGENTS.md. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -10,17 +10,29 @@ import Foundation
|
||||
/// 直播服务协议,定义直播管理和直播相册接口能力。
|
||||
@MainActor
|
||||
protocol LiveServing {
|
||||
/// 直播列表相关逻辑。
|
||||
func liveList(scenicId: Int, page: Int, pageSize: Int) async throws -> LiveListResponse
|
||||
/// 直播详情相关逻辑。
|
||||
func liveDetail(liveId: Int) async throws -> LiveEntity
|
||||
/// 直播创建相关逻辑。
|
||||
func liveCreate(_ request: LiveCreateRequest) async throws
|
||||
/// 直播启动相关逻辑。
|
||||
func liveStart(liveId: Int) async throws
|
||||
/// 直播停止相关逻辑。
|
||||
func liveStop(liveId: Int) async throws
|
||||
/// 直播Finish相关逻辑。
|
||||
func liveFinish(liveId: Int) async throws
|
||||
/// 直播Set推送Mode相关逻辑。
|
||||
func liveSetPushMode(liveId: Int, mode: Int) async throws
|
||||
/// 直播相册列表相关逻辑。
|
||||
func liveAlbumList(scenicId: Int, startTime: String?, endTime: String?, page: Int, pageSize: Int) async throws -> LiveAlbumFolderListResponse
|
||||
/// 直播相册创建文件夹相关逻辑。
|
||||
func liveAlbumCreateFolder(_ request: LiveAlbumCreateFolderRequest) async throws
|
||||
/// 直播相册删除文件夹相关逻辑。
|
||||
func liveAlbumDeleteFolder(folderId: Int) async throws
|
||||
/// 直播相册文件夹详情相关逻辑。
|
||||
func liveAlbumFolderDetail(folderId: Int) async throws -> LiveAlbumFolderItem
|
||||
/// 直播相册删除Files相关逻辑。
|
||||
func liveAlbumDeleteFiles(folderId: Int, fileIds: [Int]) async throws
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ struct LiveListResponse: Decodable {
|
||||
let page: Int
|
||||
let pageSize: Int
|
||||
|
||||
/// 枚举,定义相关常量或状态。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case items
|
||||
case total
|
||||
@ -21,6 +22,7 @@ struct LiveListResponse: Decodable {
|
||||
case pageSize = "page_size"
|
||||
}
|
||||
|
||||
/// 初始化实例。
|
||||
init(items: [LiveEntity] = [], total: Int = 0, page: Int = 1, pageSize: Int = 10) {
|
||||
self.items = items
|
||||
self.total = total
|
||||
@ -28,6 +30,7 @@ struct LiveListResponse: Decodable {
|
||||
self.pageSize = pageSize
|
||||
}
|
||||
|
||||
/// 初始化实例。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
items = (try? container.decode([LiveEntity].self, forKey: .items)) ?? []
|
||||
@ -57,6 +60,7 @@ struct LiveEntity: Decodable, Identifiable, Equatable {
|
||||
let manualPushState: Int
|
||||
let viewsCount: Int
|
||||
|
||||
/// 枚举,定义相关常量或状态。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case title
|
||||
@ -77,6 +81,7 @@ struct LiveEntity: Decodable, Identifiable, Equatable {
|
||||
case viewsCount = "views_count"
|
||||
}
|
||||
|
||||
/// 初始化实例。
|
||||
init(
|
||||
id: Int = 0,
|
||||
title: String = "",
|
||||
@ -115,6 +120,7 @@ struct LiveEntity: Decodable, Identifiable, Equatable {
|
||||
self.viewsCount = viewsCount
|
||||
}
|
||||
|
||||
/// 初始化实例。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try container.liveDecodeLossyInt(forKey: .id) ?? 0
|
||||
@ -151,6 +157,7 @@ struct LiveCreateRequest: Encodable, Equatable {
|
||||
let title: String
|
||||
let coverImg: String
|
||||
|
||||
/// 枚举,定义相关常量或状态。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case scenicId = "scenic_id"
|
||||
case title
|
||||
@ -162,6 +169,7 @@ struct LiveCreateRequest: Encodable, Equatable {
|
||||
struct LiveControlRequest: Encodable, Equatable {
|
||||
let liveId: Int
|
||||
|
||||
/// 枚举,定义相关常量或状态。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case liveId = "live_id"
|
||||
}
|
||||
@ -172,6 +180,7 @@ struct LivePushModeRequest: Encodable, Equatable {
|
||||
let liveId: Int
|
||||
let manualPushMode: Int
|
||||
|
||||
/// 枚举,定义相关常量或状态。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case liveId = "live_id"
|
||||
case manualPushMode = "manual_push_mode"
|
||||
@ -186,6 +195,7 @@ struct LiveAlbumFolderListResponse: Decodable {
|
||||
let total: Int
|
||||
let totalPages: Int
|
||||
|
||||
/// 枚举,定义相关常量或状态。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case items
|
||||
case page
|
||||
@ -194,6 +204,7 @@ struct LiveAlbumFolderListResponse: Decodable {
|
||||
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
|
||||
@ -202,6 +213,7 @@ struct LiveAlbumFolderListResponse: Decodable {
|
||||
self.totalPages = totalPages
|
||||
}
|
||||
|
||||
/// 初始化实例。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
items = (try? container.decode([LiveAlbumFolderItem].self, forKey: .items)) ?? []
|
||||
@ -220,6 +232,7 @@ struct LiveAlbumFolderItem: Decodable, Identifiable, Equatable {
|
||||
let creator: String
|
||||
let items: [LiveAlbumFileItem]
|
||||
|
||||
/// 枚举,定义相关常量或状态。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case albumId = "album_id"
|
||||
@ -228,6 +241,7 @@ struct LiveAlbumFolderItem: Decodable, Identifiable, Equatable {
|
||||
case items
|
||||
}
|
||||
|
||||
/// 初始化实例。
|
||||
init(id: Int = 0, albumId: Int = 0, name: String = "", creator: String = "", items: [LiveAlbumFileItem] = []) {
|
||||
self.id = id
|
||||
self.albumId = albumId
|
||||
@ -236,6 +250,7 @@ struct LiveAlbumFolderItem: Decodable, Identifiable, Equatable {
|
||||
self.items = items
|
||||
}
|
||||
|
||||
/// 初始化实例。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try container.liveDecodeLossyInt(forKey: .id) ?? 0
|
||||
@ -254,6 +269,7 @@ struct LiveAlbumFileItem: Decodable, Identifiable, Hashable {
|
||||
let size: Int64
|
||||
let coverImg: String?
|
||||
|
||||
/// 枚举,定义相关常量或状态。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case url
|
||||
@ -262,6 +278,7 @@ struct LiveAlbumFileItem: Decodable, Identifiable, Hashable {
|
||||
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
|
||||
@ -270,6 +287,7 @@ struct LiveAlbumFileItem: Decodable, Identifiable, Hashable {
|
||||
self.coverImg = coverImg
|
||||
}
|
||||
|
||||
/// 初始化实例。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try container.liveDecodeLossyInt(forKey: .id) ?? 0
|
||||
@ -295,6 +313,7 @@ struct LiveAlbumCreateFolderRequest: Encodable, Equatable {
|
||||
let name: String
|
||||
let items: [LiveAlbumCreateFileItem]
|
||||
|
||||
/// 枚举,定义相关常量或状态。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case scenicId = "scenic_id"
|
||||
case name
|
||||
@ -309,6 +328,7 @@ struct LiveAlbumCreateFileItem: Encodable, Equatable {
|
||||
let size: Int64
|
||||
let coverImg: String?
|
||||
|
||||
/// 枚举,定义相关常量或状态。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case url
|
||||
case type
|
||||
@ -321,6 +341,7 @@ struct LiveAlbumCreateFileItem: Encodable, Equatable {
|
||||
struct LiveAlbumDeleteFolderRequest: Encodable, Equatable {
|
||||
let folderId: Int
|
||||
|
||||
/// 枚举,定义相关常量或状态。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case folderId = "folder_id"
|
||||
}
|
||||
@ -331,6 +352,7 @@ struct LiveAlbumDeleteFilesRequest: Encodable, Equatable {
|
||||
let folderId: Int
|
||||
let fileIds: [Int]
|
||||
|
||||
/// 枚举,定义相关常量或状态。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case folderId = "folder_id"
|
||||
case fileIds = "file_ids"
|
||||
@ -346,6 +368,7 @@ struct LiveAlbumLocalUploadFile: Identifiable, Equatable {
|
||||
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
|
||||
@ -359,6 +382,7 @@ struct LiveAlbumLocalUploadFile: Identifiable, Equatable {
|
||||
}
|
||||
|
||||
private extension KeyedDecodingContainer {
|
||||
/// 直播解码宽松字符串相关逻辑。
|
||||
func liveDecodeLossyString(forKey key: Key) throws -> String {
|
||||
if let value = try? decodeIfPresent(String.self, forKey: key) {
|
||||
return value
|
||||
@ -378,6 +402,7 @@ private extension KeyedDecodingContainer {
|
||||
return ""
|
||||
}
|
||||
|
||||
/// 直播解码宽松整数相关逻辑。
|
||||
func liveDecodeLossyInt(forKey key: Key) throws -> Int? {
|
||||
if let value = try? decodeIfPresent(Int.self, forKey: key) {
|
||||
return value
|
||||
|
||||
@ -11,14 +11,17 @@ import UIKit
|
||||
final class LiveManagementViewController: ModuleTableViewController {
|
||||
private let viewModel = LiveManagementViewModel()
|
||||
|
||||
/// 视图加载完成后的 UI 初始化与数据绑定。
|
||||
override func viewDidLoad() {
|
||||
title = "直播管理"
|
||||
super.viewDidLoad()
|
||||
wireViewModel(viewModel) { }
|
||||
}
|
||||
|
||||
/// table行Count相关逻辑。
|
||||
override func tableRowCount() -> Int { viewModel.items.count }
|
||||
|
||||
/// 配置Cell展示内容。
|
||||
override func configureCell(_ cell: TitleSubtitleTableViewCell, at indexPath: IndexPath) {
|
||||
let item = viewModel.items[indexPath.row]
|
||||
cell.configure(
|
||||
@ -28,10 +31,12 @@ final class LiveManagementViewController: ModuleTableViewController {
|
||||
)
|
||||
}
|
||||
|
||||
/// 刷新Content。
|
||||
override func reloadContent() async {
|
||||
await viewModel.reload(api: services.liveAPI, scenicId: services.currentScenicId)
|
||||
}
|
||||
|
||||
/// table视图相关逻辑。
|
||||
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
guard indexPath.row >= viewModel.items.count - 2 else { return }
|
||||
Task { await viewModel.loadMore(api: services.liveAPI, scenicId: services.currentScenicId) }
|
||||
@ -44,19 +49,23 @@ extension LiveManagementViewModel: ViewModelBindable {}
|
||||
final class LiveAlbumViewController: ModuleTableViewController {
|
||||
private let viewModel = LiveAlbumViewModel()
|
||||
|
||||
/// 视图加载完成后的 UI 初始化与数据绑定。
|
||||
override func viewDidLoad() {
|
||||
title = "直播相册"
|
||||
super.viewDidLoad()
|
||||
wireViewModel(viewModel) { }
|
||||
}
|
||||
|
||||
/// table行Count相关逻辑。
|
||||
override func tableRowCount() -> Int { viewModel.folders.count }
|
||||
|
||||
/// 配置Cell展示内容。
|
||||
override func configureCell(_ cell: TitleSubtitleTableViewCell, at indexPath: IndexPath) {
|
||||
let folder = viewModel.folders[indexPath.row]
|
||||
cell.configure(title: folder.name, subtitle: folder.creator, detail: "\(folder.items.count) 张")
|
||||
}
|
||||
|
||||
/// 刷新Content。
|
||||
override func reloadContent() async {
|
||||
await viewModel.reload(api: services.liveAPI, scenicId: services.currentScenicId)
|
||||
}
|
||||
|
||||
@ -10,10 +10,12 @@ import Foundation
|
||||
|
||||
/// 直播播放地址解析器,避免把 RTMP 推流地址误当作播放地址。
|
||||
enum LivePlaybackURLResolver {
|
||||
/// playable链接相关逻辑。
|
||||
static func playableURL(from live: LiveEntity) -> URL? {
|
||||
playableURL(from: live.playbackURLCandidates)
|
||||
}
|
||||
|
||||
/// playable链接相关逻辑。
|
||||
static func playableURL(from candidates: [String]) -> URL? {
|
||||
for candidate in candidates {
|
||||
if let url = playableURL(from: candidate) {
|
||||
@ -23,6 +25,7 @@ enum LivePlaybackURLResolver {
|
||||
return nil
|
||||
}
|
||||
|
||||
/// playable链接相关逻辑。
|
||||
static func playableURL(from rawValue: String) -> URL? {
|
||||
let value = rawValue.liveTrimmed
|
||||
guard !value.isEmpty, let url = URL(string: value) else { return nil }
|
||||
@ -65,6 +68,7 @@ final class LivePlaybackViewModel {
|
||||
return false
|
||||
}
|
||||
|
||||
/// 初始化实例。
|
||||
init(live: LiveEntity? = nil, urlString: String? = nil) {
|
||||
if let live {
|
||||
load(live: live)
|
||||
@ -73,14 +77,17 @@ final class LivePlaybackViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
/// 加载数据。
|
||||
func load(live: LiveEntity) {
|
||||
load(url: LivePlaybackURLResolver.playableURL(from: live))
|
||||
}
|
||||
|
||||
/// 加载数据。
|
||||
func load(urlString: String) {
|
||||
load(url: LivePlaybackURLResolver.playableURL(from: urlString))
|
||||
}
|
||||
|
||||
/// play 业务逻辑。
|
||||
func play() {
|
||||
guard let url = playableURL else { return }
|
||||
if player == nil {
|
||||
@ -90,12 +97,14 @@ final class LivePlaybackViewModel {
|
||||
state = .playing(url)
|
||||
}
|
||||
|
||||
/// pause 业务逻辑。
|
||||
func pause() {
|
||||
guard let url = playableURL else { return }
|
||||
player?.pause()
|
||||
state = .ready(url)
|
||||
}
|
||||
|
||||
/// 刷新。
|
||||
func reload() {
|
||||
guard let url = playableURL else { return }
|
||||
releasePlayer()
|
||||
@ -103,6 +112,7 @@ final class LivePlaybackViewModel {
|
||||
state = .ready(url)
|
||||
}
|
||||
|
||||
/// release 业务逻辑。
|
||||
func release() {
|
||||
releasePlayer()
|
||||
if let url = playableURL {
|
||||
@ -112,6 +122,7 @@ final class LivePlaybackViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
/// 加载数据。
|
||||
private func load(url: URL?) {
|
||||
releasePlayer()
|
||||
guard let url else {
|
||||
@ -124,6 +135,7 @@ final class LivePlaybackViewModel {
|
||||
state = .ready(url)
|
||||
}
|
||||
|
||||
/// releasePlayer相关逻辑。
|
||||
private func releasePlayer() {
|
||||
player?.pause()
|
||||
player = nil
|
||||
|
||||
@ -47,25 +47,34 @@ enum LivePushNetworkState: Equatable {
|
||||
protocol LivePushAdapter {
|
||||
var name: String { get }
|
||||
var isAvailable: Bool { get }
|
||||
/// prepare 业务逻辑。
|
||||
func prepare(pushURL: URL) async throws
|
||||
/// start 业务逻辑。
|
||||
func start() async throws
|
||||
/// stop 业务逻辑。
|
||||
func stop() async throws
|
||||
/// dispose 业务逻辑。
|
||||
func dispose() async
|
||||
}
|
||||
|
||||
/// 权限提供者协议,隔离 AVFoundation 便于测试。
|
||||
protocol LivePermissionProviding {
|
||||
/// camera权限相关逻辑。
|
||||
func cameraPermission() async -> LivePushPermissionState
|
||||
/// microphone权限相关逻辑。
|
||||
func microphonePermission() async -> LivePushPermissionState
|
||||
}
|
||||
|
||||
/// 网络监听协议,隔离 NWPathMonitor 便于测试。
|
||||
protocol LiveNetworkMonitoring: AnyObject {
|
||||
var currentState: LivePushNetworkState { get }
|
||||
/// start 业务逻辑。
|
||||
func start(_ onChange: @escaping @Sendable (LivePushNetworkState) -> Void)
|
||||
/// stop 业务逻辑。
|
||||
func stop()
|
||||
}
|
||||
|
||||
/// LivePushReadiness错误类型定义。
|
||||
enum LivePushReadinessError: LocalizedError, Equatable {
|
||||
case missingPushURL
|
||||
case invalidPushURL
|
||||
@ -89,32 +98,41 @@ enum LivePushReadinessError: LocalizedError, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
/// UnsupportedLivePushAdapter,业务类型定义。
|
||||
struct UnsupportedLivePushAdapter: LivePushAdapter {
|
||||
let name = "未接入推流 SDK"
|
||||
let isAvailable = false
|
||||
|
||||
/// prepare 业务逻辑。
|
||||
func prepare(pushURL: URL) async throws {
|
||||
throw LivePushReadinessError.sdkUnavailable
|
||||
}
|
||||
|
||||
/// start 业务逻辑。
|
||||
func start() async throws {
|
||||
throw LivePushReadinessError.sdkUnavailable
|
||||
}
|
||||
|
||||
/// stop 业务逻辑。
|
||||
func stop() async throws {}
|
||||
|
||||
/// dispose 业务逻辑。
|
||||
func dispose() async {}
|
||||
}
|
||||
|
||||
/// SystemLivePermission能力提供者,封装外部依赖。
|
||||
struct SystemLivePermissionProvider: LivePermissionProviding {
|
||||
/// camera权限相关逻辑。
|
||||
func cameraPermission() async -> LivePushPermissionState {
|
||||
await permission(for: .video)
|
||||
}
|
||||
|
||||
/// microphone权限相关逻辑。
|
||||
func microphonePermission() async -> LivePushPermissionState {
|
||||
await permission(for: .audio)
|
||||
}
|
||||
|
||||
/// permission 业务逻辑。
|
||||
private func permission(for mediaType: AVMediaType) async -> LivePushPermissionState {
|
||||
switch AVCaptureDevice.authorizationStatus(for: mediaType) {
|
||||
case .authorized:
|
||||
@ -130,6 +148,7 @@ struct SystemLivePermissionProvider: LivePermissionProviding {
|
||||
}
|
||||
}
|
||||
|
||||
/// SystemLiveNetworkMonitor 类型定义。
|
||||
final class SystemLiveNetworkMonitor: LiveNetworkMonitoring {
|
||||
|
||||
var onChange: (() -> Void)?
|
||||
@ -137,6 +156,7 @@ final class SystemLiveNetworkMonitor: LiveNetworkMonitoring {
|
||||
private let queue = DispatchQueue(label: "com.suixinkan.live.network")
|
||||
private(set) var currentState: LivePushNetworkState = .unknown
|
||||
|
||||
/// start 业务逻辑。
|
||||
func start(_ onChange: @escaping @Sendable (LivePushNetworkState) -> Void) {
|
||||
monitor.pathUpdateHandler = { [weak self] path in
|
||||
let state = Self.state(from: path)
|
||||
@ -146,10 +166,12 @@ final class SystemLiveNetworkMonitor: LiveNetworkMonitoring {
|
||||
monitor.start(queue: queue)
|
||||
}
|
||||
|
||||
/// stop 业务逻辑。
|
||||
func stop() {
|
||||
monitor.cancel()
|
||||
}
|
||||
|
||||
/// state 业务逻辑。
|
||||
private static func state(from path: NWPath) -> LivePushNetworkState {
|
||||
guard path.status == .satisfied else { return .unavailable }
|
||||
if path.usesInterfaceType(.wifi) { return .wifi }
|
||||
@ -177,6 +199,7 @@ final class LivePushReadinessViewModel {
|
||||
private let adapter: any LivePushAdapter
|
||||
private var pushURL: URL?
|
||||
|
||||
/// 初始化实例。
|
||||
init(
|
||||
permissionProvider: any LivePermissionProviding = SystemLivePermissionProvider(),
|
||||
networkMonitor: any LiveNetworkMonitoring = SystemLiveNetworkMonitor(),
|
||||
@ -190,6 +213,7 @@ final class LivePushReadinessViewModel {
|
||||
self.networkState = networkMonitor.currentState
|
||||
}
|
||||
|
||||
/// 配置展示内容。
|
||||
func configure(pushURL rawValue: String) {
|
||||
let value = rawValue.liveTrimmed
|
||||
guard !value.isEmpty else {
|
||||
@ -206,6 +230,7 @@ final class LivePushReadinessViewModel {
|
||||
errorMessage = nil
|
||||
}
|
||||
|
||||
/// startMonitoring相关逻辑。
|
||||
func startMonitoring() {
|
||||
networkState = networkMonitor.currentState
|
||||
networkMonitor.start { [weak self] state in
|
||||
@ -215,10 +240,12 @@ final class LivePushReadinessViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
/// stopMonitoring相关逻辑。
|
||||
func stopMonitoring() {
|
||||
networkMonitor.stop()
|
||||
}
|
||||
|
||||
/// refreshPermissions相关逻辑。
|
||||
func refreshPermissions() async {
|
||||
async let camera = permissionProvider.cameraPermission()
|
||||
async let microphone = permissionProvider.microphonePermission()
|
||||
@ -226,6 +253,7 @@ final class LivePushReadinessViewModel {
|
||||
microphonePermission = await microphone
|
||||
}
|
||||
|
||||
/// runDiagnostics相关逻辑。
|
||||
func runDiagnostics() throws {
|
||||
do {
|
||||
try validateReadiness()
|
||||
@ -240,6 +268,7 @@ final class LivePushReadinessViewModel {
|
||||
errorMessage = nil
|
||||
}
|
||||
|
||||
/// prepare 业务逻辑。
|
||||
func prepare() async throws {
|
||||
try validateReadiness()
|
||||
guard let pushURL else { throw LivePushReadinessError.missingPushURL }
|
||||
@ -253,6 +282,7 @@ final class LivePushReadinessViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
/// start推送相关逻辑。
|
||||
func startPush() async throws {
|
||||
try validateReadiness()
|
||||
guard adapter.isAvailable else {
|
||||
@ -265,11 +295,13 @@ final class LivePushReadinessViewModel {
|
||||
errorMessage = nil
|
||||
}
|
||||
|
||||
/// stop推送相关逻辑。
|
||||
func stopPush() async {
|
||||
try? await adapter.stop()
|
||||
running = false
|
||||
}
|
||||
|
||||
/// dispose 业务逻辑。
|
||||
func dispose() async {
|
||||
stopMonitoring()
|
||||
await adapter.dispose()
|
||||
@ -277,6 +309,7 @@ final class LivePushReadinessViewModel {
|
||||
prepared = false
|
||||
}
|
||||
|
||||
/// 校验Readiness输入或状态。
|
||||
private func validateReadiness() throws {
|
||||
guard pushURL != nil else {
|
||||
throw LivePushReadinessError.missingPushURL
|
||||
|
||||
@ -104,6 +104,7 @@ final class LiveManagementViewModel {
|
||||
await reload(api: api, scenicId: scenicId, showLoading: false)
|
||||
}
|
||||
|
||||
/// 加载Page数据。
|
||||
private func loadPage(api: any LiveServing, scenicId: Int, page: Int) async throws {
|
||||
let response = try await api.liveList(scenicId: scenicId, page: page, pageSize: pageSize)
|
||||
if page == 1 {
|
||||
@ -118,6 +119,7 @@ final class LiveManagementViewModel {
|
||||
hasMore = items.count < total
|
||||
}
|
||||
|
||||
/// 重置状态。
|
||||
private func reset() {
|
||||
clearListAndDetail()
|
||||
loading = false
|
||||
@ -125,6 +127,7 @@ final class LiveManagementViewModel {
|
||||
errorMessage = nil
|
||||
}
|
||||
|
||||
/// 清空列表并详情。
|
||||
private func clearListAndDetail() {
|
||||
items = []
|
||||
detail = nil
|
||||
@ -143,6 +146,7 @@ final class LiveDetailViewModel {
|
||||
var actionInFlight = false { didSet { onChange?() } }
|
||||
var errorMessage: String? { didSet { onChange?() } }
|
||||
|
||||
/// 初始化实例。
|
||||
init(detail: LiveEntity) {
|
||||
self.detail = detail
|
||||
}
|
||||
@ -265,6 +269,7 @@ final class LiveAlbumViewModel {
|
||||
await reload(api: api, scenicId: scenicId, showLoading: false)
|
||||
}
|
||||
|
||||
/// 加载Page数据。
|
||||
private func loadPage(api: any LiveServing, scenicId: Int, page: Int) async throws {
|
||||
let response = try await api.liveAlbumList(
|
||||
scenicId: scenicId,
|
||||
@ -284,6 +289,7 @@ final class LiveAlbumViewModel {
|
||||
hasMore = folders.count < total
|
||||
}
|
||||
|
||||
/// 重置状态。
|
||||
private func reset() {
|
||||
clearFolders()
|
||||
loading = false
|
||||
@ -291,6 +297,7 @@ final class LiveAlbumViewModel {
|
||||
errorMessage = nil
|
||||
}
|
||||
|
||||
/// 清空Folders。
|
||||
private func clearFolders() {
|
||||
folders = []
|
||||
page = 1
|
||||
@ -377,6 +384,7 @@ final class LiveAlbumPreviewViewModel {
|
||||
var loading = false { didSet { onChange?() } }
|
||||
var errorMessage: String? { didSet { onChange?() } }
|
||||
|
||||
/// 初始化实例。
|
||||
init(folderId: Int, startIndex: Int = 0, summary: LiveAlbumFolderItem? = nil) {
|
||||
self.folderId = folderId
|
||||
currentIndex = max(startIndex, 0)
|
||||
|
||||
Reference in New Issue
Block a user