Files
e-party-iOS/yana/Utils/APILoading/APILoadingManager.swift
edwinQQQ d35071d3de feat: 更新动态点赞与加载状态管理以提升用户体验
- 在DetailFeature和FeedListFeature中增强点赞功能的状态管理,确保用户交互流畅。
- 新增API加载效果视图,提升用户在操作过程中的反馈体验。
- 更新视图组件以支持点赞加载状态,优化用户界面交互。
- 改进错误处理逻辑,确保在API请求失败时提供友好的错误提示。
2025-07-28 16:05:22 +08:00

152 lines
4.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Foundation
import SwiftUI
import Combine
// MARK: - API Loading Manager
/// API
///
///
/// - API
/// - loading
/// -
/// - 线
@MainActor
class APILoadingManager: ObservableObject {
// MARK: - Properties
///
static let shared = APILoadingManager()
///
@Published private(set) var loadingItems: [APILoadingItem] = []
///
private var errorCleanupTasks: [UUID: DispatchWorkItem] = [:]
///
private init() {}
// MARK: - Public Methods
/// loading
/// - Parameters:
/// - shouldShowLoading: loading
/// - shouldShowError:
/// - Returns: ID
func startLoading(shouldShowLoading: Bool = true, shouldShowError: Bool = true) -> UUID {
let loadingId = UUID()
let loadingItem = APILoadingItem(
id: loadingId,
state: .loading,
shouldShowError: shouldShowError,
shouldShowLoading: shouldShowLoading
)
loadingItems.append(loadingItem)
return loadingId
}
/// loading
/// - Parameter id: ID
func finishLoading(_ id: UUID) {
removeLoading(id)
}
/// loading
/// - Parameters:
/// - id: ID
/// - errorMessage:
func setError(_ id: UUID, errorMessage: String) {
guard let index = loadingItems.firstIndex(where: { $0.id == id }) else { return }
let currentItem = loadingItems[index]
if currentItem.shouldShowError {
let errorItem = APILoadingItem(
id: id,
state: .error(message: errorMessage),
shouldShowError: true,
shouldShowLoading: currentItem.shouldShowLoading
)
loadingItems[index] = errorItem
setupErrorCleanup(for: id)
} else {
loadingItems.removeAll { $0.id == id }
}
}
///
/// - Parameter id: ID
private func removeLoading(_ id: UUID) {
cancelErrorCleanup(for: id)
loadingItems.removeAll { $0.id == id }
}
///
func clearAll() {
errorCleanupTasks.values.forEach { $0.cancel() }
errorCleanupTasks.removeAll()
loadingItems.removeAll()
}
// MARK: - Computed Properties
/// loading
var hasActiveLoading: Bool {
loadingItems.contains { $0.state == .loading && $0.shouldDisplay }
}
///
var hasActiveError: Bool {
loadingItems.contains { $0.isError && $0.shouldDisplay }
}
// MARK: - Private Methods
///
/// - Parameter id: ID
private func setupErrorCleanup(for id: UUID) {
let workItem = DispatchWorkItem { [weak self] in
self?.removeLoading(id)
}
errorCleanupTasks[id] = workItem
DispatchQueue.main.asyncAfter(
deadline: .now() + APILoadingConfiguration.errorDisplayDuration,
execute: workItem
)
}
///
/// - Parameter id: ID
private func cancelErrorCleanup(for id: UUID) {
errorCleanupTasks[id]?.cancel()
errorCleanupTasks.removeValue(forKey: id)
}
}
// MARK: - Convenience Extensions
extension APILoadingManager {
/// 便 loading
/// - Parameters:
/// - shouldShowLoading: loading
/// - shouldShowError:
/// - operation:
/// - Returns:
@MainActor
func withLoading<T: Sendable>(
shouldShowLoading: Bool = true,
shouldShowError: Bool = true,
operation: @escaping () async throws -> T
) async -> Result<T, Error> {
let loadingId = startLoading(
shouldShowLoading: shouldShowLoading,
shouldShowError: shouldShowError
)
do {
let result = try await operation()
finishLoading(loadingId)
return .success(result)
} catch {
setError(loadingId, errorMessage: error.localizedDescription)
return .failure(error)
}
}
}
// MARK: - Global Convenience Methods
/// 便fire-and-forget
/// - Parameters:
/// - id: ID
/// - errorMessage:
func setAPILoadingErrorSync(_ id: UUID, errorMessage: String) {
Task {
await MainActor.run {
APILoadingManager.shared.setError(id, errorMessage: errorMessage)
}
}
}