| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- //
- // LNIMChatViewController.swift
- // Lanu
- //
- // Created by OneeChan on 2025/12/4.
- //
- import Foundation
- import UIKit
- import SnapKit
- extension UIView {
- func pushToChat(uid: String) {
- let vc = LNIMChatViewController(userId: uid)
- // LNIMManager.shared.saveDraftFor("c2c_" + uid, draft: " ")
- // LNIMManager.shared.saveDraftFor("c2c_" + uid, draft: nil)
- navigationController?.pushViewController(vc, animated: true)
- }
- }
- class LNIMChatViewController: LNViewController {
- private let viewModel: LNIMChatViewModel
-
- private let topMenu = LNIMChatTopMenuView()
-
- private let stackView = UIStackView()
- private let skillView = LNIMChatGameMateSkillView()
- private let orderView = LNIMChatGameMateOrderView()
-
- private let tableView = UITableView()
- private let bottomMenu = LNIMChatInputMenuView()
-
- init(userId: String) {
- viewModel = LNIMChatViewModel(userId: userId)
- super.init(nibName: nil, bundle: nil)
- }
-
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- showNavigationBar = false
- setupViews()
- LNEventDeliver.addObserver(self)
-
- loadMessageList()
- }
-
- override func viewWillDisappear(_ animated: Bool) {
- super.viewWillDisappear(animated)
-
- view.endEditing(true)
-
- viewModel.cleanUnread()
- }
- }
- extension LNIMChatViewController {
- private func loadMessageList() {
- viewModel.loadNextPage { [weak self] success, isFirst in
- guard let self else { return }
- guard success else { return }
-
- let oldHeight = tableView.contentSize.height
- tableView.reloadData()
- if isFirst {
- tableView.scrollToBottom(animated: false)
- } else {
- tableView.layoutIfNeeded()
-
- let newOffset = tableView.contentSize.height - oldHeight
-
- tableView.scrollRectToVisible(
- .init(x: 0, y: newOffset,
- width: tableView.bounds.width,
- height: tableView.bounds.height),
- animated: false)
- }
- }
- }
-
- private func loadUserInfo() {
-
- }
- }
- extension LNIMChatViewController: LNIMChatViewModelNotify {
- func onIMMessageDataChanged(index: Int, type: LNIMChatDataSourceChangeType, toBottom: Bool) {
- switch type {
- case .insert: tableView.reloadData()
- case .delete: tableView.reloadData()
- case .reload: tableView.reloadRows(at: [.init(row: index, section: 0)], with: .fade)
- }
-
- if toBottom {
- tableView.scrollToBottom()
- }
- }
-
- func onIMMessageDatasChanged(indexs: [IndexPath], type: LNIMChatDataSourceChangeType, toBottom: Bool) {
- switch type {
- case .insert: tableView.reloadData()
- case .delete: tableView.reloadData()
- case .reload: tableView.reloadRows(at: indexs, with: .fade)
- }
-
- if toBottom {
- tableView.scrollToBottom()
- }
- }
- }
- extension LNIMChatViewController: UITableViewDataSource, UITableViewDelegate {
- func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- viewModel.allMessage.count
- }
-
- func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- let data = viewModel.allMessage[indexPath.row]
-
- if !data.imMessage.isSelf, data.imMessage.needReadReceipt,
- !data.imMessage.isRead {
- V2TIMManager.sharedInstance().sendMessageReadReceipts(messageList: [data.imMessage]) { }
- fail: { code, err in }
- }
-
- switch data.type {
- case .system:
- let cell = tableView.dequeueReusableCell(withIdentifier: LNIMChatSystemMessageCell.className, for: indexPath) as! LNIMChatSystemMessageCell
- cell.update(data)
- return cell
- case .image:
- let cell = tableView.dequeueReusableCell(withIdentifier: LNIMChatImageMessageCell.className, for: indexPath) as! LNIMChatImageMessageCell
- cell.update(data, viewModel: viewModel)
- return cell
- case .text:
- let cell = tableView.dequeueReusableCell(withIdentifier: LNIMChatTextMessageCell.className, for: indexPath) as! LNIMChatTextMessageCell
- cell.update(data, viewModel: viewModel)
- return cell
- case .voice:
- let cell = tableView.dequeueReusableCell(withIdentifier: LNIMChatVoiceMessageCell.className, for: indexPath) as! LNIMChatVoiceMessageCell
- cell.update(data, viewModel: viewModel)
- return cell
- case .custom(let subType):
- switch subType {
- case .playmate_order:
- let cell = tableView.dequeueReusableCell(withIdentifier: LNIMChatOrderMessageCell.className, for: indexPath) as! LNIMChatOrderMessageCell
- cell.update(data, viewModel: viewModel)
- return cell
- default:
- break
- }
- case .none:
- break
- }
- return tableView.dequeueReusableCell(withIdentifier: LNIMChatUnknownMessageCell.className, for: indexPath)
- }
-
- func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
- guard scrollView == tableView else { return }
- if tableView.contentOffset.y <= 0 {
- loadMessageList()
- }
- }
-
- func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
- guard scrollView == tableView else { return }
- if tableView.contentOffset.y <= 0 {
- loadMessageList()
- }
- }
- }
- extension LNIMChatViewController: LNKeyboardNotify {
- func onKeybaordWillShow(curInput: UIView?, keyboardHeight: CGFloat) {
- guard curInput?.isDescendant(of: view) == true else { return }
- tableView.scrollToBottom(animated: false)
- }
-
- func onKeybaordShow(curInput: UIView?, keyboardHeight: CGFloat) {
- guard curInput?.isDescendant(of: view) == true else { return }
- tableView.scrollToBottom(animated: true)
- }
- }
- extension LNIMChatViewController {
- private func setupViews() {
- view.backgroundColor = .primary_1
-
- topMenu.viewModel = viewModel
- view.addSubview(topMenu)
- topMenu.snp.makeConstraints { make in
- make.directionalHorizontalEdges.equalToSuperview()
- make.top.equalToSuperview()
- }
-
- stackView.axis = .vertical
- stackView.spacing = 8
- view.addSubview(stackView)
- stackView.snp.makeConstraints { make in
- make.directionalHorizontalEdges.equalToSuperview()
- make.top.equalTo(topMenu.snp.bottom)
- }
-
- skillView.viewModel = viewModel
- stackView.addArrangedSubview(skillView)
- orderView.viewModel = viewModel
- stackView.addArrangedSubview(orderView)
-
- bottomMenu.viewModel = viewModel
- view.addSubview(bottomMenu)
- bottomMenu.snp.makeConstraints { make in
- make.directionalHorizontalEdges.equalToSuperview()
- make.bottom.equalToSuperview()
- }
-
- tableView.backgroundColor = .clear
- tableView.separatorStyle = .none
- tableView.allowsSelection = false
- tableView.register(LNIMChatImageMessageCell.self, forCellReuseIdentifier: LNIMChatImageMessageCell.className)
- tableView.register(LNIMChatSystemMessageCell.self, forCellReuseIdentifier: LNIMChatSystemMessageCell.className)
- tableView.register(LNIMChatTextMessageCell.self, forCellReuseIdentifier: LNIMChatTextMessageCell.className)
- tableView.register(LNIMChatVoiceMessageCell.self, forCellReuseIdentifier: LNIMChatVoiceMessageCell.className)
- tableView.register(LNIMChatUnknownMessageCell.self, forCellReuseIdentifier: LNIMChatUnknownMessageCell.className)
- tableView.register(LNIMChatOrderMessageCell.self, forCellReuseIdentifier: LNIMChatOrderMessageCell.className)
- tableView.dataSource = self
- tableView.delegate = self
- tableView.contentInset = .init(top: 8, left: 0, bottom: 0, right: 0)
- view.addSubview(tableView)
- tableView.snp.makeConstraints { make in
- make.directionalHorizontalEdges.equalToSuperview()
- make.top.equalTo(stackView.snp.bottom)
- make.bottom.equalTo(bottomMenu.snp.top)
- }
-
- tableView.onTap { [weak self] in
- guard let self else { return }
- bottomMenu.hideInput()
- }
- }
- }
|