Files
suixinkan_uikit/suixinkan/Features/MessageCenter/ViewModels/MessageCenterViewModels.swift
汉秋 caeeb9a1cf feat: 接入消息未读数、全部已读与首页红点角标同步。
固定消息中心为首页入口,并在推送到达、已读后刷新桌面角标。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 18:02:43 +08:00

196 lines
6.0 KiB
Swift
Raw Permalink 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.

//
// 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?()
}
}