Files
suixinkan_uikit/suixinkan/Features/MessageCenter/ViewModels/MessageCenterViewModels.swift
T

206 lines
6.4 KiB
Swift

//
// MessageCenterViewModels.swift
// suixinkan
//
import Foundation
/// 消息中心列表页 ViewModel,负责分页、刷新和未读点击处理。
final class MessageCenterViewModel {
private(set) var items: [MessageItem] = []
private(set) var isLoading = false
private(set) var isRefreshing = false
private(set) var isLoadingMore = false
private(set) var isMarkingAllAsRead = false
private(set) var canLoadMore = false
var onStateChange: (() -> Void)?
var onShowMessage: ((String) -> Void)?
var onOpenDetail: ((MessageItem) -> Void)?
var onUnreadMessageCountChange: ((Int) -> Void)?
private let pageSize = 20
private var lastId = 0
/// 首次加载消息列表。
func loadInitial(api: any MessageCenterServing) async {
await load(reset: true, showLoading: true, api: api)
}
/// 下拉刷新消息列表。
func refresh(api: any MessageCenterServing) async {
guard !isLoading else { return }
isRefreshing = true
notifyStateChange()
await load(reset: true, showLoading: false, api: api)
}
/// 滚动到底部附近时加载更多。
func loadMoreIfNeeded(lastVisibleIndex: Int, api: any MessageCenterServing) async {
guard lastVisibleIndex >= items.count - 4 else { return }
await load(reset: false, showLoading: false, api: api)
}
/// 点击消息:未读先标记已读,成功后进入详情;已读直接进入详情。
func selectMessage(id: Int, api: any MessageCenterServing) async {
guard let item = items.first(where: { $0.id == id }) else { return }
if item.isRead {
onOpenDetail?(item)
return
}
isLoading = true
notifyStateChange()
defer {
isLoading = false
notifyStateChange()
}
do {
let response = try await api.markAsRead(messageId: id)
let readItem = item.markedRead()
items = items.map { $0.id == id ? readItem : $0 }
notifyUnreadMessageCountChanged(response.unreadCount)
onOpenDetail?(readItem)
} catch is CancellationError {
return
} catch {
onShowMessage?("标记已读失败")
}
}
/// 将当前账号的全部消息标记已读,成功时返回服务端最新统计。
func markAllAsRead(api: any MessageCenterServing) async -> MessageReadAllResponse? {
guard !isLoading, !isMarkingAllAsRead else { return nil }
isMarkingAllAsRead = true
notifyStateChange()
defer {
isMarkingAllAsRead = false
notifyStateChange()
}
do {
let response = try await api.markAllAsRead()
items = items.map { $0.isRead ? $0 : $0.markedRead() }
notifyUnreadMessageCountChanged(response.unreadCount)
onShowMessage?(response.updatedCount > 0 ? "已全部标记为已读" : "暂无未读消息")
return response
} catch is CancellationError {
return nil
} catch {
onShowMessage?("全部标记已读失败,请稍后重试")
return nil
}
}
/// 删除后从本地列表移除消息。
func removeMessage(id: Int) {
items.removeAll { $0.id == id }
notifyStateChange()
}
private func load(reset: Bool, showLoading: Bool, api: any MessageCenterServing) async {
if reset {
lastId = 0
canLoadMore = false
} else {
guard canLoadMore, !isLoading, !isLoadingMore else { return }
isLoadingMore = true
}
isLoading = showLoading
notifyStateChange()
defer {
isLoading = false
isRefreshing = false
isLoadingMore = false
notifyStateChange()
}
do {
let response = try await api.list(lastId: reset ? 0 : lastId, limit: pageSize, unread: 0)
lastId = response.lastId
items = reset ? response.items : items + response.items
canLoadMore = response.hasMore && response.lastId > 0
} catch is CancellationError {
return
} catch {
if reset {
items = []
lastId = 0
canLoadMore = false
}
onShowMessage?(error.localizedDescription)
}
}
private func notifyStateChange() {
onStateChange?()
}
private func notifyUnreadMessageCountChanged(_ count: Int) {
let normalizedCount = max(count, 0)
onUnreadMessageCountChange?(normalizedCount)
NotificationCenter.default.post(
name: NotificationName.unreadMessageCountDidChange,
object: nil,
userInfo: [NotificationUserInfoKey.unreadCount: normalizedCount]
)
}
}
/// 消息详情页 ViewModel,负责删除当前消息。
final class MessageDetailViewModel {
private(set) var message: MessageItem
private(set) var isDeleting = false
var onStateChange: (() -> Void)?
var onShowMessage: ((String) -> Void)?
var onDeleted: ((Int) -> Void)?
/// 初始化消息详情 ViewModel。
init(message: MessageItem) {
self.message = message
}
/// AI 修图任务通知在详情页展示任务入口。
var showsAIRetouchTaskAction: Bool {
message.isAIRetouchTaskNotification
}
/// 当前消息携带的 AI 修图批次 ID;缺失时由页面降级进入任务列表。
var aiRetouchBatchId: Int? {
message.aiRetouchBatchId
}
/// 删除当前消息。
func delete(api: any MessageCenterServing) async {
guard message.id > 0 else {
onShowMessage?("消息ID无效")
return
}
isDeleting = true
notifyStateChange()
defer {
isDeleting = false
notifyStateChange()
}
do {
try await api.delete(messageId: message.id)
onShowMessage?("删除成功")
onDeleted?(message.id)
} catch is CancellationError {
return
} catch {
let detail = error.localizedDescription.trimmingCharacters(in: .whitespacesAndNewlines)
onShowMessage?("删除失败: \(detail.isEmpty ? "请稍后重试" : detail)")
}
}
private func notifyStateChange() {
onStateChange?()
}
}