206 lines
6.4 KiB
Swift
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?()
|
|
}
|
|
}
|