196 lines
6.0 KiB
Swift
196 lines
6.0 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
|
||
}
|
||
|
||
/// 删除当前消息。
|
||
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?()
|
||
}
|
||
}
|