373 lines
12 KiB
Swift
373 lines
12 KiB
Swift
//
|
|
// MailboxViewController.swift
|
|
// QuickLocation
|
|
//
|
|
|
|
import UIKit
|
|
import RxSwift
|
|
import MJRefresh
|
|
|
|
final class MailboxViewController: BaseViewController {
|
|
override var isNavigationBarHidden: Bool { true }
|
|
|
|
private enum LoadKind {
|
|
case initial
|
|
case refresh
|
|
case more
|
|
case category
|
|
}
|
|
|
|
private var rootView: MailboxView!
|
|
private var messages: [MailboxMessage] = []
|
|
private var selectedCategory: MailboxCategory = .interaction
|
|
private var currentPage = 0
|
|
private var total = 0
|
|
private var reachedEnd = false
|
|
private var isLoading = false
|
|
private var pendingCategory: MailboxCategory?
|
|
|
|
override func loadView() {
|
|
rootView = MailboxView(frame: UIScreen.main.bounds)
|
|
view = rootView
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
setupTableView()
|
|
bindActions()
|
|
rootView.selectCategory(selectedCategory)
|
|
updateContentState(showLoading: true)
|
|
loadPage(
|
|
1,
|
|
kind: .initial,
|
|
replacing: true,
|
|
category: selectedCategory,
|
|
baselineCount: 0
|
|
)
|
|
}
|
|
|
|
private var visibleMessages: [MailboxMessage] {
|
|
messages.filter { $0.category == selectedCategory }
|
|
}
|
|
|
|
private func setupTableView() {
|
|
rootView.tableView.dataSource = self
|
|
rootView.tableView.delegate = self
|
|
|
|
let header = MJRefreshNormalHeader { [weak self] in
|
|
self?.refreshMessages()
|
|
}
|
|
header.addFeedbackGenerator()
|
|
header.stateLabel?.isHidden = true
|
|
rootView.tableView.mj_header = header
|
|
|
|
let footer = MJRefreshAutoNormalFooter { [weak self] in
|
|
self?.loadMoreMessages()
|
|
}
|
|
footer.stateLabel?.isHidden = true
|
|
footer.isHidden = true
|
|
rootView.tableView.mj_footer = footer
|
|
}
|
|
|
|
private func bindActions() {
|
|
rootView.backButton.addTarget(self, action: #selector(backAction), for: .touchUpInside)
|
|
rootView.clearButton.addTarget(self, action: #selector(clearAction), for: .touchUpInside)
|
|
rootView.onSelectCategory = { [weak self] category in
|
|
self?.selectCategory(category)
|
|
}
|
|
}
|
|
|
|
private func selectCategory(_ category: MailboxCategory) {
|
|
guard selectedCategory != category else { return }
|
|
selectedCategory = category
|
|
rootView.selectCategory(category)
|
|
rootView.tableView.reloadData()
|
|
rootView.tableView.setContentOffset(
|
|
CGPoint(x: 0, y: -rootView.tableView.adjustedContentInset.top),
|
|
animated: false
|
|
)
|
|
updateContentState(showLoading: isLoading)
|
|
ensureMessagesLoaded(for: category)
|
|
}
|
|
|
|
private func refreshMessages() {
|
|
guard !isLoading else {
|
|
rootView.tableView.mj_header?.endRefreshing()
|
|
return
|
|
}
|
|
loadPage(
|
|
1,
|
|
kind: .refresh,
|
|
replacing: true,
|
|
category: selectedCategory,
|
|
baselineCount: 0
|
|
)
|
|
}
|
|
|
|
private func loadMoreMessages() {
|
|
guard !isLoading, !reachedEnd else {
|
|
finishRefreshControls()
|
|
return
|
|
}
|
|
let category = selectedCategory
|
|
loadPage(
|
|
currentPage + 1,
|
|
kind: .more,
|
|
replacing: false,
|
|
category: category,
|
|
baselineCount: messages(for: category).count
|
|
)
|
|
}
|
|
|
|
private func ensureMessagesLoaded(for category: MailboxCategory) {
|
|
guard messages(for: category).isEmpty, !reachedEnd else {
|
|
updateContentState(showLoading: false)
|
|
return
|
|
}
|
|
guard !isLoading else {
|
|
pendingCategory = category
|
|
return
|
|
}
|
|
loadPage(
|
|
currentPage + 1,
|
|
kind: .category,
|
|
replacing: false,
|
|
category: category,
|
|
baselineCount: 0
|
|
)
|
|
}
|
|
|
|
private func loadPage(
|
|
_ page: Int,
|
|
kind: LoadKind,
|
|
replacing: Bool,
|
|
category: MailboxCategory,
|
|
baselineCount: Int
|
|
) {
|
|
guard !isLoading else { return }
|
|
isLoading = true
|
|
pendingCategory = nil
|
|
updateContentState(showLoading: visibleMessages.isEmpty)
|
|
|
|
UserService.mailboxList(page: page)
|
|
.subscribe(onNext: { [weak self] response in
|
|
guard let self else { return }
|
|
guard let data = response.data else {
|
|
self.finishLoadingWithError(message: response.message ?? "消息加载失败")
|
|
return
|
|
}
|
|
if replacing {
|
|
self.messages = data.messages
|
|
} else {
|
|
self.mergeMessages(data.messages)
|
|
}
|
|
self.currentPage = max(data.page, page)
|
|
self.total = max(data.total, self.messages.count)
|
|
self.reachedEnd = self.calculateReachedEnd(data: data)
|
|
self.rootView.tableView.reloadData()
|
|
self.updateUnreadCounts()
|
|
|
|
let categoryCount = self.messages(for: category).count
|
|
if self.selectedCategory == category,
|
|
categoryCount <= baselineCount,
|
|
!self.reachedEnd {
|
|
self.isLoading = false
|
|
self.loadPage(
|
|
self.currentPage + 1,
|
|
kind: kind,
|
|
replacing: false,
|
|
category: category,
|
|
baselineCount: baselineCount
|
|
)
|
|
return
|
|
}
|
|
|
|
self.isLoading = false
|
|
self.finishRefreshControls()
|
|
self.updateContentState(showLoading: false)
|
|
self.loadPendingCategoryIfNeeded()
|
|
}, onError: { [weak self] error in
|
|
self?.finishLoadingWithError(message: error.localizedDescription)
|
|
})
|
|
.disposed(by: disposeBag)
|
|
}
|
|
|
|
private func mergeMessages(_ newMessages: [MailboxMessage]) {
|
|
var indices = Dictionary(uniqueKeysWithValues: messages.enumerated().map { ($0.element.id, $0.offset) })
|
|
for message in newMessages {
|
|
if let index = indices[message.id] {
|
|
messages[index] = message
|
|
} else {
|
|
indices[message.id] = messages.count
|
|
messages.append(message)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func calculateReachedEnd(data: MailboxListData) -> Bool {
|
|
if data.total == 0 || data.messages.isEmpty {
|
|
return true
|
|
}
|
|
if messages.count >= data.total {
|
|
return true
|
|
}
|
|
let pageSize = max(data.size, 1)
|
|
return max(data.page, currentPage) * pageSize >= data.total
|
|
}
|
|
|
|
private func loadPendingCategoryIfNeeded() {
|
|
guard let category = pendingCategory ?? (visibleMessages.isEmpty ? selectedCategory : nil) else { return }
|
|
pendingCategory = nil
|
|
ensureMessagesLoaded(for: category)
|
|
}
|
|
|
|
private func finishLoadingWithError(message: String) {
|
|
isLoading = false
|
|
finishRefreshControls()
|
|
updateContentState(showLoading: false)
|
|
DLToast.showError(text: message)
|
|
}
|
|
|
|
private func finishRefreshControls() {
|
|
rootView.tableView.mj_header?.endRefreshing()
|
|
if reachedEnd {
|
|
rootView.tableView.mj_footer?.endRefreshingWithNoMoreData()
|
|
} else {
|
|
rootView.tableView.mj_footer?.endRefreshing()
|
|
}
|
|
}
|
|
|
|
private func messages(for category: MailboxCategory) -> [MailboxMessage] {
|
|
messages.filter { $0.category == category }
|
|
}
|
|
|
|
private func updateUnreadCounts() {
|
|
var counts: [MailboxCategory: Int] = [:]
|
|
MailboxCategory.displayOrder.forEach { category in
|
|
counts[category] = messages(for: category).filter { !$0.isRead }.count
|
|
}
|
|
rootView.updateUnreadCounts(counts)
|
|
}
|
|
|
|
private func updateContentState(showLoading: Bool) {
|
|
rootView.updateState(
|
|
hasMessages: !visibleMessages.isEmpty,
|
|
isLoading: showLoading,
|
|
reachedEnd: reachedEnd
|
|
)
|
|
}
|
|
|
|
private func openMessage(_ message: MailboxMessage) {
|
|
DLToast.showLoading()
|
|
UserService.mailboxDetail(id: message.id)
|
|
.subscribe(onNext: { [weak self] response in
|
|
guard let self else { return }
|
|
DLToast.dismiss()
|
|
guard let responseMessage = response.data else {
|
|
DLToast.showError(text: response.message ?? "消息详情加载失败")
|
|
return
|
|
}
|
|
var detail = self.mergeDetail(responseMessage, fallback: message)
|
|
detail.isRead = true
|
|
self.updateMessage(detail)
|
|
let categoryMessages = self.messages(for: detail.category)
|
|
let detailController = MailboxDetailViewController(
|
|
messages: categoryMessages,
|
|
initialMessage: detail
|
|
)
|
|
detailController.onMessageRead = { [weak self] updatedMessage in
|
|
self?.updateMessage(updatedMessage)
|
|
}
|
|
self.present(detailController, animated: true)
|
|
}, onError: { error in
|
|
DLToast.dismiss()
|
|
DLToast.showError(text: error.localizedDescription)
|
|
})
|
|
.disposed(by: disposeBag)
|
|
}
|
|
|
|
private func updateMessage(_ updatedMessage: MailboxMessage) {
|
|
guard let index = messages.firstIndex(where: { $0.id == updatedMessage.id }) else { return }
|
|
messages[index] = updatedMessage
|
|
rootView.tableView.reloadData()
|
|
updateUnreadCounts()
|
|
}
|
|
|
|
private func mergeDetail(_ detail: MailboxMessage, fallback: MailboxMessage) -> MailboxMessage {
|
|
var merged = detail
|
|
if merged.id == 0 { merged.id = fallback.id }
|
|
if merged.title.isEmpty { merged.title = fallback.title }
|
|
if merged.content.isEmpty { merged.content = fallback.content }
|
|
if merged.senderId.isEmpty { merged.senderId = fallback.senderId }
|
|
if merged.senderIcon.isEmpty { merged.senderIcon = fallback.senderIcon }
|
|
if merged.modifyTime.isEmpty { merged.modifyTime = fallback.modifyTime }
|
|
if MailboxCategory(rawValue: merged.type) == nil { merged.type = fallback.type }
|
|
return merged
|
|
}
|
|
|
|
@objc private func backAction() {
|
|
navigationController?.popViewController(animated: true)
|
|
}
|
|
|
|
@objc private func clearAction() {
|
|
guard !messages.isEmpty else {
|
|
DLToast.show(text: "暂无消息")
|
|
return
|
|
}
|
|
showConfirmPop(
|
|
title: "清空全部消息",
|
|
message: "清空后无法恢复,是否继续?",
|
|
confirmText: "清空",
|
|
confirmBlock: { [weak self] in
|
|
self?.requestClearAll()
|
|
},
|
|
cancelText: "取消"
|
|
)
|
|
}
|
|
|
|
private func requestClearAll() {
|
|
DLToast.showLoading()
|
|
UserService.clearMailbox()
|
|
.subscribe(onNext: { [weak self] response in
|
|
guard let self else { return }
|
|
DLToast.dismiss()
|
|
self.messages = []
|
|
self.currentPage = 0
|
|
self.total = 0
|
|
self.reachedEnd = true
|
|
self.pendingCategory = nil
|
|
self.rootView.tableView.reloadData()
|
|
self.updateUnreadCounts()
|
|
self.finishRefreshControls()
|
|
self.updateContentState(showLoading: false)
|
|
DLToast.showSuccess(text: "已清空")
|
|
}, onError: { error in
|
|
DLToast.dismiss()
|
|
DLToast.showError(text: error.localizedDescription)
|
|
})
|
|
.disposed(by: disposeBag)
|
|
}
|
|
|
|
}
|
|
|
|
extension MailboxViewController: UITableViewDataSource, UITableViewDelegate {
|
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
visibleMessages.count
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
guard let cell = tableView.dequeueReusableCell(
|
|
withIdentifier: MailboxMessageCell.reuseIdentifier,
|
|
for: indexPath
|
|
) as? MailboxMessageCell else {
|
|
return UITableViewCell()
|
|
}
|
|
cell.configure(message: visibleMessages[indexPath.row])
|
|
return cell
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
openMessage(visibleMessages[indexPath.row])
|
|
}
|
|
}
|