diff --git a/Bark.xcodeproj/project.pbxproj b/Bark.xcodeproj/project.pbxproj index 9ad12de..3226fe5 100644 --- a/Bark.xcodeproj/project.pbxproj +++ b/Bark.xcodeproj/project.pbxproj @@ -115,6 +115,7 @@ 068EC15827ED99C900D5D11E /* ServerListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 068EC15727ED99C900D5D11E /* ServerListViewController.swift */; }; 068EC15A27ED99E700D5D11E /* ServerListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 068EC15927ED99E700D5D11E /* ServerListViewModel.swift */; }; 068F66B3247BD84C00DAD25A /* MessageListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 068F66B2247BD84C00DAD25A /* MessageListViewController.swift */; }; + 0699473D2D223094008D5E40 /* CustomTapTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0699473C2D223094008D5E40 /* CustomTapTextView.swift */; }; 06AE3118266F4E2E00B39FBB /* GroupFilterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AE3117266F4E2E00B39FBB /* GroupFilterViewController.swift */; }; 06AE311A266F4E6600B39FBB /* GroupFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AE3119266F4E6600B39FBB /* GroupFilterViewModel.swift */; }; 06AE311C266F54A500B39FBB /* GroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AE311B266F54A500B39FBB /* GroupTableViewCell.swift */; }; @@ -357,6 +358,7 @@ 068EC15727ED99C900D5D11E /* ServerListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListViewController.swift; sourceTree = ""; }; 068EC15927ED99E700D5D11E /* ServerListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListViewModel.swift; sourceTree = ""; }; 068F66B2247BD84C00DAD25A /* MessageListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageListViewController.swift; sourceTree = ""; }; + 0699473C2D223094008D5E40 /* CustomTapTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTapTextView.swift; sourceTree = ""; }; 06AE3117266F4E2E00B39FBB /* GroupFilterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupFilterViewController.swift; sourceTree = ""; }; 06AE3119266F4E6600B39FBB /* GroupFilterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupFilterViewModel.swift; sourceTree = ""; }; 06AE311B266F54A500B39FBB /* GroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupTableViewCell.swift; sourceTree = ""; }; @@ -681,6 +683,7 @@ 066890092D19495400E106F2 /* MessageList */ = { isa = PBXGroup; children = ( + 0699473C2D223094008D5E40 /* CustomTapTextView.swift */, 066890072D1946D500E106F2 /* MessageItemView.swift */, 0667D191247D162C005DE2ED /* MessageTableViewCell.swift */, 0668900A2D19525400E106F2 /* ShowLessAndClearView.swift */, @@ -1238,6 +1241,7 @@ 0642B55C27EB149900453D91 /* MutableTextCellViewModel.swift in Sources */, 062B98C8251B27AE004562E7 /* UINavigationItem+Extension.swift in Sources */, 060481EE250F404500BC9799 /* SoundsViewController.swift in Sources */, + 0699473D2D223094008D5E40 /* CustomTapTextView.swift in Sources */, 06EEF333291CCFF400CA228A /* CryptoSettingController.swift in Sources */, 0603706D20E23EC000F4CA05 /* BarkSFSafariViewController.swift in Sources */, 06AE311A266F4E6600B39FBB /* GroupFilterViewModel.swift in Sources */, diff --git a/Controller/MessageListViewController.swift b/Controller/MessageListViewController.swift index 8fb60dc..5695b30 100644 --- a/Controller/MessageListViewController.swift +++ b/Controller/MessageListViewController.swift @@ -104,6 +104,23 @@ class MessageListViewController: BaseViewController { .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 数据源 @@ -120,6 +137,10 @@ class MessageListViewController: BaseViewController { 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): @@ -146,6 +167,10 @@ class MessageListViewController: BaseViewController { 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, max(0, totalCount - messages.count), messages) cell.isExpanded = self.expandedGroup.contains(title) return cell @@ -161,31 +186,11 @@ class MessageListViewController: BaseViewController { return } - let itemSelected = tableView.rx.itemSelected.asDriver().compactMap { [weak self] indexPath -> Int? in - guard let self else { return nil } - 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) - } - return nil - } - } - - return indexPath.row - } - 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(), - itemSelected: itemSelected, delete: getBatchDeleteDriver(), groupToggleTap: groupBtn.rx.tap.asDriver(), searchText: navigationItem.searchController!.searchBar.rx.text.asObservable() @@ -200,11 +205,6 @@ class MessageListViewController: BaseViewController { .drive(tableView.rx.items(dataSource: dataSource)) .disposed(by: rx.disposeBag) - // message操作alert - output.alertMessage.drive(onNext: { [weak self] message in - self?.alertMessage(message: message.0, indexPath: IndexPath(row: message.1, section: 0)) - }).disposed(by: rx.disposeBag) - // 选择群组 output.type .drive(onNext: { [weak self] type in @@ -266,7 +266,7 @@ class MessageListViewController: BaseViewController { .asDriver(onErrorDriveWith: .empty()) } - private func alertMessage(message: String, indexPath: IndexPath) { + 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 @@ -279,11 +279,9 @@ class MessageListViewController: BaseViewController { alertController.addAction(copyAction) alertController.addAction(cancelAction) if UIDevice.current.userInterfaceIdiom == .pad { - if let cell = self.tableView.cellForRow(at: indexPath) { - alertController.popoverPresentationController?.sourceView = self.tableView - alertController.popoverPresentationController?.sourceRect = cell.frame - alertController.modalPresentationStyle = .popover - } + alertController.popoverPresentationController?.sourceView = sourceView.superview + alertController.popoverPresentationController?.sourceRect = sourceView.frame + alertController.modalPresentationStyle = .popover } self.navigationController?.present(alertController, animated: true, completion: nil) diff --git a/Controller/MessageListViewModel.swift b/Controller/MessageListViewModel.swift index f036dea..0d79c40 100644 --- a/Controller/MessageListViewModel.swift +++ b/Controller/MessageListViewModel.swift @@ -34,8 +34,6 @@ class MessageListViewModel: ViewModel, ViewModelType { var loadMore: Driver /// 删除 var itemDelete: Driver - /// 点击 - var itemSelected: Driver /// 批量删除 var delete: Driver /// 切换群组和列表显示样式 @@ -49,8 +47,6 @@ class MessageListViewModel: ViewModel, ViewModelType { var messages: Driver<[MessageSection]> /// 刷新控件状态 var refreshAction: Driver - /// 点击后,弹出提示 - var alertMessage: Driver<(String, Int)> /// 群组过滤 var type: Driver /// 标题 @@ -189,26 +185,6 @@ class MessageListViewModel: ViewModel, ViewModelType { } func transform(input: Input) -> Output { - let alertMessage = input.itemSelected.map { [weak self] index in - guard let results = self?.results else { - return ("", 0) - } - let message = results[index] - - var copyContent: String = "" - if let title = message.title { - copyContent += "\(title)\n" - } - if let body = message.body { - copyContent += "\(body)\n" - } - if let url = message.url { - copyContent += "\(url)\n" - } - copyContent = String(copyContent.prefix(copyContent.count - 1)) - - return (copyContent, index) - } // 标题 let titleRelay = BehaviorRelay(value: NSLocalizedString("historyMessage")) // 数据源 @@ -384,7 +360,6 @@ class MessageListViewModel: ViewModel, ViewModelType { return Output( messages: messagesRelay.asDriver(onErrorJustReturn: []), refreshAction: refreshAction.asDriver(), - alertMessage: alertMessage, type: Driver.merge(messageTypeChanged.asDriver(), Driver.just(self.type)), title: titleRelay.asDriver(), groupToggleButtonHidden: Driver.just(groupToggleButtonHidden) diff --git a/View/MessageList/CustomTapTextView.swift b/View/MessageList/CustomTapTextView.swift new file mode 100644 index 0000000..efde392 --- /dev/null +++ b/View/MessageList/CustomTapTextView.swift @@ -0,0 +1,65 @@ +// +// CustomTapTextView.swift +// Bark +// +// Created by huangfeng on 12/30/24. +// Copyright © 2024 Fin. All rights reserved. +// + +import UIKit + +/// 可以自定义点击事件的 UITextView,同时保留 UITextView 的所有其他手势 +/// 此 TextView 不可编辑, 不可滚动 +class CustomTapTextView: UITextView, UIGestureRecognizerDelegate { + /// 点击手势,如果有选中文字,则不触发 + private lazy var tapGesture = UITapGestureRecognizer(target: self, action: #selector(tap)) + /// 双击手势,只是为了让 tapGesture 不要在双击选中文本时触发,没有其他作用 + private let doubleTapGesture = UITapGestureRecognizer() + + /// 额外的单击事件 + var customTapAction: (() -> Void)? + + init() { + super.init(frame: .zero, textContainer: nil) + + self.backgroundColor = UIColor.clear + self.isEditable = false + self.dataDetectorTypes = [.phoneNumber, .link] + self.isScrollEnabled = false + self.textContainerInset = .zero + self.textContainer.lineFragmentPadding = 0 + + tapGesture.delegate = self + tapGesture.require(toFail: doubleTapGesture) + self.addGestureRecognizer(tapGesture) + + doubleTapGesture.numberOfTapsRequired = 2 + doubleTapGesture.delegate = self + self.addGestureRecognizer(doubleTapGesture) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc func tap() { + self.customTapAction?() + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer == doubleTapGesture { + return true + } + return false + } + + override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer == tapGesture { + if self.selectedRange.length > 0 { + return false + } + } + return super.gestureRecognizerShouldBegin(gestureRecognizer) + } +} diff --git a/View/MessageList/MessageItemView.swift b/View/MessageList/MessageItemView.swift index 42e1b75..a4c9db9 100644 --- a/View/MessageList/MessageItemView.swift +++ b/View/MessageList/MessageItemView.swift @@ -25,14 +25,8 @@ class MessageItemView: UIView { return view }() - let bodyLabel: UITextView = { - let label = UITextView() - label.backgroundColor = UIColor.clear - label.isEditable = false - label.dataDetectorTypes = [.phoneNumber, .link] - label.isScrollEnabled = false - label.textContainerInset = .zero - label.textContainer.lineFragmentPadding = 0 + let bodyLabel: CustomTapTextView = { + let label = CustomTapTextView() label.font = UIFont.preferredFont(ofSize: 14) label.adjustsFontForContentSizeCategory = true label.textColor = BKColor.grey.darken4 @@ -73,6 +67,8 @@ class MessageItemView: UIView { } } + var tapAction: ((_ message: MessageItemModel, _ sourceView: UIView) -> Void)? + init() { super.init(frame: .zero) self.backgroundColor = BKColor.background.primary @@ -93,6 +89,13 @@ class MessageItemView: UIView { } self.dateLabel.text = self.message?.dateText }).disposed(by: rx.disposeBag) + + self.bodyLabel.customTapAction = { [weak self] in + guard let self, let message = self.message else { return } + self.tapAction?(message, self) + } + + panel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap))) } @available(*, unavailable) @@ -122,6 +125,11 @@ class MessageItemView: UIView { make.edges.equalTo(panel) } } + + @objc func tap() { + guard let message else { return } + self.tapAction?(message, self) + } } extension MessageItemView { diff --git a/View/MessageList/MessageTableViewCell.swift b/View/MessageList/MessageTableViewCell.swift index f79994e..40189a9 100644 --- a/View/MessageList/MessageTableViewCell.swift +++ b/View/MessageList/MessageTableViewCell.swift @@ -23,6 +23,12 @@ class MessageTableViewCell: UITableViewCell { } } + var tapAction: ((_ message: MessageItemModel, _ sourceView: UIView) -> Void)? { + didSet { + messageView.tapAction = tapAction + } + } + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.selectionStyle = .none @@ -136,6 +142,8 @@ class MessageGroupTableViewCell: UITableViewCell { groupHeader.clearAction = newValue } } + + var tapAction: ((_ message: MessageItemModel, _ sourceView: UIView) -> Void)? = nil /// 查看群组所有消息 var showGroupMessageAction: ((_ group: String?) -> Void)? = nil @@ -170,6 +178,12 @@ class MessageGroupTableViewCell: UITableViewCell { moreView.gestureRecognizers?.first?.rx.event.subscribe(onNext: { [weak self] _ in self?.showGroupMessageAction?(self?.messages.first?.group) }).disposed(by: self.rx.disposeBag) + + for view in messageViews { + view.tapAction = { [weak self] message, sourceView in + self?.tapAction?(message, sourceView) + } + } refreshViewState() }