Bark/Controller/MessageListViewController.swift

349 lines
16 KiB
Swift
Raw 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.

//
// MessageListViewController.swift
// Bark
//
// Created by huangfeng on 2020/5/25.
// Copyright © 2020 Fin. All rights reserved.
//
import Material
import MJRefresh
import RealmSwift
import RxCocoa
import RxDataSources
import RxSwift
import UIKit
enum MessageDeleteType: Int {
case lastHour = 0
case today
case todayAndYesterday
case allTime
var string: String {
return [
NSLocalizedString("lastHour"),
NSLocalizedString("today"),
NSLocalizedString("todayAndYesterday"),
NSLocalizedString("allTime")
][self.rawValue]
}
}
class MessageListViewController: BaseViewController<MessageListViewModel> {
let deleteButton: UIBarButtonItem = {
let btn = BKButton()
btn.setImage(UIImage(named: "baseline_delete_outline_black_24pt"), for: .normal)
btn.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
return UIBarButtonItem(customView: btn)
}()
let groupButton: UIBarButtonItem = {
let btn = BKButton()
btn.setImage(UIImage(named: "group_expand")?.withRenderingMode(.alwaysTemplate), for: .normal)
btn.setImage(UIImage(named: "group_collapse")?.withRenderingMode(.alwaysTemplate), for: .selected)
btn.imageView?.tintColor = BKColor.black
btn.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
return UIBarButtonItem(customView: btn)
}()
private var expandedGroup: Set<String> = []
lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.separatorStyle = .none
tableView.backgroundColor = BKColor.background.primary
tableView.register(MessageTableViewCell.self, forCellReuseIdentifier: "\(MessageTableViewCell.self)")
tableView.register(MessageGroupTableViewCell.self, forCellReuseIdentifier: "\(MessageGroupTableViewCell.self)")
// LargeTitle LargeTitle
//
// tableView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
// contentInset header
tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 20))
tableView.rx.setDelegate(self).disposed(by: rx.disposeBag)
tableView.mj_footer = MJRefreshAutoFooter()
tableView.refreshControl = UIRefreshControl()
return tableView
}()
override func makeUI() {
navigationItem.searchController = UISearchController(searchResultsController: nil)
navigationItem.searchController?.obscuresBackgroundDuringPresentation = false
navigationItem.searchController?.delegate = self
navigationItem.setBarButtonItems(items: [deleteButton, groupButton], position: .right)
self.view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
// tab
Client.shared.currentTabBarController?
.tabBarItemDidClick
.filter { $0 == .messageHistory }
.subscribe(onNext: { [weak self] _ in
self?.scrollToTop()
}).disposed(by: self.rx.disposeBag)
// APP5
var lastAutoRefreshdate = Date()
NotificationCenter.default.rx
.notification(UIApplication.willEnterForegroundNotification)
.filter { _ in
let now = Date()
if now.timeIntervalSince1970 - lastAutoRefreshdate.timeIntervalSince1970 > 60 * 5 {
lastAutoRefreshdate = now
return true
}
return false
}
.subscribe(onNext: { [weak self] _ in
self?.tableView.refreshControl?.sendActions(for: .valueChanged)
}).disposed(by: rx.disposeBag)
//
tableView.rx.itemSelected.subscribe(onNext: { [weak self] indexPath in
guard let self else { return }
if let cell = self.tableView.cellForRow(at: indexPath) as? MessageGroupTableViewCell {
if !cell.isExpanded {
UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.2) {
self.tableView.performBatchUpdates {
cell.isExpanded = true
}
}
if let groupName = cell.cellData?.groupName {
self.expandedGroup.insert(groupName)
}
}
}
}).disposed(by: rx.disposeBag)
}
// tableView
private lazy var dataSource = RxTableViewSectionedAnimatedDataSource<MessageSection>(
animationConfiguration: AnimationConfiguration(
insertAnimation: .none,
reloadAnimation: .none,
deleteAnimation: .left
),
configureCell: { _, tableView, _, item -> UITableViewCell in
switch item {
case .message(let message):
guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(MessageTableViewCell.self)") as? MessageTableViewCell else {
return UITableViewCell()
}
cell.tapAction = { [weak self, weak cell] message, sourceView in
guard let self else { return }
self.alertMessage(message: message.attributedText?.string ?? "", sourceView: sourceView)
}
cell.message = message
return cell
case .messageGroup(let title, let totalCount, let messages):
guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(MessageGroupTableViewCell.self)") as? MessageGroupTableViewCell else {
return UITableViewCell()
}
cell.showLessAction = { [weak self, weak cell] in
guard let self else { return }
UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.2) {
self.tableView.performBatchUpdates {
cell?.isExpanded = false
}
if let groupName = cell?.cellData?.groupName {
self.expandedGroup.remove(groupName)
}
}
}
cell.showGroupMessageAction = { [weak self] group in
let viewModel = MessageListViewModel(sourceType: .group(group))
let controller = MessageListViewController(viewModel: viewModel)
self?.navigationController?.pushViewController(controller, animated: true)
}
cell.clearAction = { [weak self, weak cell] in
guard let self, let cell, let indexPath = self.tableView.indexPath(for: cell) else { return }
self.tableView.dataSource?.tableView?(self.tableView, commit: .delete, forRowAt: indexPath)
}
cell.tapAction = { [weak self, weak cell] message, sourceView in
guard let self else { return }
self.alertMessage(message: message.attributedText?.string ?? "", sourceView: sourceView)
}
cell.cellData = (title, totalCount, messages)
cell.isExpanded = self.expandedGroup.contains(title)
return cell
}
}, canEditRowAtIndexPath: { _, _ in
true
}
)
override func bindViewModel() {
guard let groupBtn = groupButton.customView as? BKButton else {
return
}
let output = viewModel.transform(
input: MessageListViewModel.Input(
refresh: tableView.refreshControl!.rx.controlEvent(.valueChanged).asDriver(),
loadMore: tableView.mj_footer!.rx.refresh.asDriver(),
itemDelete: tableView.rx.modelDeleted(MessageListCellItem.self).asDriver(),
delete: getBatchDeleteDriver(),
groupToggleTap: groupBtn.rx.tap.asDriver(),
searchText: navigationItem.searchController!.searchBar.rx.text.asObservable()
))
// tableView
output.refreshAction
.drive(tableView.rx.refreshAction)
.disposed(by: rx.disposeBag)
output.messages
.drive(tableView.rx.items(dataSource: dataSource))
.disposed(by: rx.disposeBag)
//
output.type
.drive(onNext: { [weak self] type in
(self?.groupButton.customView as? UIButton)?.isSelected = type == .group
}).disposed(by: rx.disposeBag)
//
output.title
.drive(self.navigationItem.rx.title).disposed(by: rx.disposeBag)
output.groupToggleButtonHidden
.drive((groupButton.customView as! UIButton).rx.isHidden).disposed(by: rx.disposeBag)
}
private func getBatchDeleteDriver() -> Driver<MessageDeleteType> {
guard let deleteBtn = deleteButton.customView as? BKButton else {
return Driver.never()
}
return deleteBtn.rx
.tap
.flatMapLatest { _ -> PublishRelay<MessageDeleteType> in
let relay = PublishRelay<MessageDeleteType>()
func alert(_ type: MessageDeleteType) {
let alertController = UIAlertController(title: nil, message: "\(NSLocalizedString("clearFrom"))\n\(type.string)", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("clear"), style: .destructive, handler: { _ in
relay.accept(type)
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: nil))
self.navigationController?.present(alertController, animated: true, completion: nil)
}
let alertController = UIAlertController(title: nil, message: NSLocalizedString("clearFrom"), preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: NSLocalizedString("lastHour"), style: .default, handler: { _ in
alert(.lastHour)
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("today"), style: .default, handler: { _ in
alert(.today)
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("todayAndYesterday"), style: .default, handler: { _ in
alert(.todayAndYesterday)
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("allTime"), style: .default, handler: { _ in
alert(.allTime)
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: nil))
if UIDevice.current.userInterfaceIdiom == .pad {
alertController.modalPresentationStyle = .popover
if #available(iOS 16.0, *) {
alertController.popoverPresentationController?.sourceItem = self.deleteButton
} else {
alertController.popoverPresentationController?.barButtonItem = self.deleteButton
}
}
self.navigationController?.present(alertController, animated: true, completion: nil)
return relay
}
.asDriver(onErrorDriveWith: .empty())
}
private func alertMessage(message: String, sourceView: UIView) {
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let copyAction = UIAlertAction(title: NSLocalizedString("CopyAll"), style: .default, handler: { [weak self]
(_: UIAlertAction) in
UIPasteboard.general.string = message
self?.showSnackbar(text: NSLocalizedString("Copy"))
})
let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: { _ in })
alertController.addAction(copyAction)
alertController.addAction(cancelAction)
if UIDevice.current.userInterfaceIdiom == .pad {
alertController.popoverPresentationController?.sourceView = sourceView.superview
alertController.popoverPresentationController?.sourceRect = sourceView.frame
alertController.modalPresentationStyle = .popover
}
self.navigationController?.present(alertController, animated: true, completion: nil)
}
private func scrollToTop() {
if self.tableView.visibleCells.count > 0 {
self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
}
}
}
extension MessageListViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let action = UIContextualAction(style: .destructive, title: NSLocalizedString("removeMessage")) { [weak self] _, _, actionPerformed in
guard let self else { return }
if self.tableView.cellForRow(at: indexPath) is MessageTableViewCell {
//
self.tableView.dataSource?.tableView?(self.tableView, commit: .delete, forRowAt: indexPath)
actionPerformed(true)
return
}
//
let alertView = UIAlertController(title: nil, message: NSLocalizedString("removeNotice"), preferredStyle: .alert)
alertView.addAction(UIAlertAction(title: NSLocalizedString("removeMessage"), style: .destructive, handler: { _ in
self.tableView.dataSource?.tableView?(self.tableView, commit: .delete, forRowAt: indexPath)
actionPerformed(true)
}))
alertView.addAction(UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: { _ in
actionPerformed(false)
}))
self.present(alertView, animated: true, completion: nil)
}
let configuration = UISwipeActionsConfiguration(actions: [action])
return configuration
}
}
extension MessageListViewController: UISearchControllerDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if self.navigationItem.searchController?.searchBar.isFirstResponder == true {
self.navigationItem.searchController?.searchBar.resignFirstResponder()
}
}
func willDismissSearchController(_ searchController: UISearchController) {
if !searchController.searchBar.isFirstResponder {
/*
searchBar searchBar.rx.text
searchBar.rx.text
nil searchBarsearchBar.text nil
keywordkeyword
text searchBar.rx.text
actions
*/
searchController.searchBar.searchTextField.text = nil
searchController.searchBar.searchTextField.sendActions(for: .editingDidEnd)
}
}
}