MessageListViewController.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. //
  2. // MessageListViewController.swift
  3. // Bark
  4. //
  5. // Created by huangfeng on 2020/5/25.
  6. // Copyright © 2020 Fin. All rights reserved.
  7. //
  8. import Material
  9. import MJRefresh
  10. import RealmSwift
  11. import RxCocoa
  12. import RxDataSources
  13. import RxSwift
  14. import UIKit
  15. enum MessageDeleteType: Int {
  16. case lastHour = 0
  17. case today
  18. case todayAndYesterday
  19. case allTime
  20. var string: String {
  21. return [
  22. NSLocalizedString("lastHour"),
  23. NSLocalizedString("today"),
  24. NSLocalizedString("todayAndYesterday"),
  25. NSLocalizedString("allTime")
  26. ][self.rawValue]
  27. }
  28. }
  29. class MessageListViewController: BaseViewController<MessageListViewModel> {
  30. let deleteButton: UIBarButtonItem = {
  31. let btn = BKButton()
  32. btn.setImage(UIImage(named: "baseline_delete_outline_black_24pt"), for: .normal)
  33. btn.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
  34. return UIBarButtonItem(customView: btn)
  35. }()
  36. let groupButton: UIBarButtonItem = {
  37. let btn = BKButton()
  38. btn.setImage(UIImage(named: "baseline_folder_open_black_24pt"), for: .normal)
  39. btn.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
  40. return UIBarButtonItem(customView: btn)
  41. }()
  42. let tableView: UITableView = {
  43. let tableView = UITableView()
  44. tableView.separatorStyle = .none
  45. tableView.backgroundColor = BKColor.background.primary
  46. tableView.register(MessageTableViewCell.self, forCellReuseIdentifier: "\(MessageTableViewCell.self)")
  47. // 设置了这个后,第一次进页面 LargeTitle 就会收缩成小标题,不设置这个LargeTitle就是大标题显示
  48. // 谁特么能整的明白这个?
  49. // tableView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
  50. // 替代 contentInset 设置一个 header
  51. tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 20))
  52. return tableView
  53. }()
  54. override func makeUI() {
  55. navigationItem.searchController = UISearchController(searchResultsController: nil)
  56. navigationItem.searchController?.obscuresBackgroundDuringPresentation = false
  57. navigationItem.searchController?.delegate = self
  58. navigationItem.setBarButtonItems(items: [deleteButton, groupButton], position: .right)
  59. self.view.addSubview(tableView)
  60. tableView.snp.makeConstraints { make in
  61. make.edges.equalToSuperview()
  62. }
  63. tableView.rx.setDelegate(self).disposed(by: rx.disposeBag)
  64. tableView.mj_footer = MJRefreshAutoFooter()
  65. tableView.refreshControl = UIRefreshControl()
  66. // 点击tab按钮,回到顶部
  67. Client.shared.currentTabBarController?
  68. .tabBarItemDidClick
  69. .filter { $0 == .messageHistory }
  70. .subscribe(onNext: { [weak self] _ in
  71. self?.scrollToTop()
  72. }).disposed(by: self.rx.disposeBag)
  73. // 打开APP时,历史消息列表距离上次刷新超过1小时,则自动刷新一下
  74. var lastAutoRefreshdate = Date()
  75. NotificationCenter.default.rx
  76. .notification(UIApplication.willEnterForegroundNotification)
  77. .filter { _ in
  78. let now = Date()
  79. if now.timeIntervalSince1970 - lastAutoRefreshdate.timeIntervalSince1970 > 60 * 60 {
  80. lastAutoRefreshdate = now
  81. return true
  82. }
  83. return false
  84. }
  85. .subscribe(onNext: { [weak self] _ in
  86. self?.tableView.refreshControl?.sendActions(for: .valueChanged)
  87. }).disposed(by: rx.disposeBag)
  88. }
  89. override func bindViewModel() {
  90. guard let deleteBtn = deleteButton.customView as? BKButton else {
  91. return
  92. }
  93. let batchDelete = deleteBtn.rx
  94. .tap
  95. .flatMapLatest { _ -> PublishRelay<MessageDeleteType> in
  96. let relay = PublishRelay<MessageDeleteType>()
  97. func alert(_ type: MessageDeleteType) {
  98. let alertController = UIAlertController(title: nil, message: "\(NSLocalizedString("clearFrom"))\n\(type.string)", preferredStyle: .alert)
  99. alertController.addAction(UIAlertAction(title: NSLocalizedString("clear"), style: .destructive, handler: { _ in
  100. relay.accept(type)
  101. }))
  102. alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: nil))
  103. self.navigationController?.present(alertController, animated: true, completion: nil)
  104. }
  105. let alertController = UIAlertController(title: nil, message: NSLocalizedString("clearFrom"), preferredStyle: .actionSheet)
  106. alertController.addAction(UIAlertAction(title: NSLocalizedString("lastHour"), style: .default, handler: { _ in
  107. alert(.lastHour)
  108. }))
  109. alertController.addAction(UIAlertAction(title: NSLocalizedString("today"), style: .default, handler: { _ in
  110. alert(.today)
  111. }))
  112. alertController.addAction(UIAlertAction(title: NSLocalizedString("todayAndYesterday"), style: .default, handler: { _ in
  113. alert(.todayAndYesterday)
  114. }))
  115. alertController.addAction(UIAlertAction(title: NSLocalizedString("allTime"), style: .default, handler: { _ in
  116. alert(.allTime)
  117. }))
  118. alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: nil))
  119. if UIDevice.current.userInterfaceIdiom == .pad {
  120. alertController.modalPresentationStyle = .popover
  121. if #available(iOS 16.0, *) {
  122. alertController.popoverPresentationController?.sourceItem = self.deleteButton
  123. } else {
  124. alertController.popoverPresentationController?.barButtonItem = self.deleteButton
  125. }
  126. }
  127. self.navigationController?.present(alertController, animated: true, completion: nil)
  128. return relay
  129. }
  130. guard let groupBtn = groupButton.customView as? BKButton else {
  131. return
  132. }
  133. let output = viewModel.transform(
  134. input: MessageListViewModel.Input(
  135. refresh: tableView.refreshControl!.rx.controlEvent(.valueChanged).asDriver(),
  136. loadMore: tableView.mj_footer!.rx.refresh.asDriver(),
  137. itemDelete: tableView.rx.itemDeleted.asDriver().map { $0.row },
  138. itemSelected: tableView.rx.itemSelected.asDriver().map { $0.row },
  139. delete: batchDelete.asDriver(onErrorDriveWith: .empty()),
  140. groupTap: groupBtn.rx.tap.asDriver(),
  141. searchText: navigationItem.searchController!.searchBar.rx.text.asObservable()
  142. ))
  143. // tableView 刷新状态
  144. output.refreshAction
  145. .drive(tableView.rx.refreshAction)
  146. .disposed(by: rx.disposeBag)
  147. // tableView 数据源
  148. let dataSource = RxTableViewSectionedAnimatedDataSource<MessageSection>(
  149. animationConfiguration: AnimationConfiguration(
  150. insertAnimation: .none,
  151. reloadAnimation: .none,
  152. deleteAnimation: .left
  153. ),
  154. configureCell: { _, tableView, _, item -> UITableViewCell in
  155. guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(MessageTableViewCell.self)") as? MessageTableViewCell else {
  156. return UITableViewCell()
  157. }
  158. cell.bindViewModel(model: item)
  159. return cell
  160. }, canEditRowAtIndexPath: { _, _ in
  161. true
  162. }
  163. )
  164. output.messages
  165. .drive(tableView.rx.items(dataSource: dataSource))
  166. .disposed(by: rx.disposeBag)
  167. // message操作alert
  168. output.alertMessage.drive(onNext: { [weak self] message in
  169. self?.alertMessage(message: message.0, indexPath: IndexPath(row: message.1, section: 0))
  170. }).disposed(by: rx.disposeBag)
  171. // 选择群组
  172. output.groupFilter
  173. .drive(onNext: { [weak self] groupModel in
  174. self?.navigationController?.present(BarkNavigationController(rootViewController: GroupFilterViewController(viewModel: groupModel)), animated: true, completion: nil)
  175. }).disposed(by: rx.disposeBag)
  176. // 标题
  177. output.title
  178. .drive(self.navigationItem.rx.title).disposed(by: rx.disposeBag)
  179. }
  180. func alertMessage(message: String, indexPath: IndexPath) {
  181. let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  182. let copyAction = UIAlertAction(title: NSLocalizedString("CopyAll"), style: .default, handler: { [weak self]
  183. (_: UIAlertAction) in
  184. UIPasteboard.general.string = message
  185. self?.showSnackbar(text: NSLocalizedString("Copy"))
  186. })
  187. let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: { _ in })
  188. alertController.addAction(copyAction)
  189. alertController.addAction(cancelAction)
  190. if UIDevice.current.userInterfaceIdiom == .pad {
  191. alertController.modalPresentationStyle = .popover
  192. if let cell = self.tableView.cellForRow(at: indexPath) {
  193. alertController.popoverPresentationController?.sourceView = self.tableView
  194. alertController.popoverPresentationController?.sourceRect = cell.frame
  195. }
  196. }
  197. self.navigationController?.present(alertController, animated: true, completion: nil)
  198. }
  199. private func scrollToTop() {
  200. if self.tableView.visibleCells.count > 0 {
  201. self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
  202. }
  203. }
  204. }
  205. extension MessageListViewController: UITableViewDelegate {
  206. func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
  207. let action = UIContextualAction(style: .destructive, title: NSLocalizedString("removeMessage")) { [weak self] _, _, actionPerformed in
  208. self?.tableView.dataSource?.tableView?(self!.tableView, commit: .delete, forRowAt: indexPath)
  209. actionPerformed(true)
  210. }
  211. let configuration = UISwipeActionsConfiguration(actions: [action])
  212. return configuration
  213. }
  214. }
  215. extension MessageListViewController: UISearchControllerDelegate {
  216. func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
  217. if self.navigationItem.searchController?.searchBar.isFirstResponder == true {
  218. self.navigationItem.searchController?.searchBar.resignFirstResponder()
  219. }
  220. }
  221. func willDismissSearchController(_ searchController: UISearchController) {
  222. if !searchController.searchBar.isFirstResponder {
  223. /*
  224. searchBar 不在焦点时,点击搜索框右边的取消按钮时,不会触发 searchBar.rx.text 更改事件
  225. searchBar.rx.text 将一直保留为最后的文本
  226. 但我们预期是要更新为 nil 的,因为再次点击searchBar,searchBar.text 显示的是 nil
  227. 可能对用户造成困惑,搜索框里没有输入任何keyword,但消息列表却被错误的keyword过滤了
  228. 另外直接给 text 赋值,并不能触发 searchBar.rx.text,
  229. 需要手动发送一下actions
  230. */
  231. searchController.searchBar.searchTextField.text = nil
  232. searchController.searchBar.searchTextField.sendActions(for: .editingDidEnd)
  233. }
  234. }
  235. }