diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index da4ac06010..ad0541a19d 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -22,7 +22,8 @@ extension ConversationVC: ContextMenuActionDelegate, SendMediaNavDelegate, AttachmentApprovalViewControllerDelegate, - GifPickerViewControllerDelegate + GifPickerViewControllerDelegate, + UIGestureRecognizerDelegate { // MARK: - Open Settings @@ -33,6 +34,11 @@ extension ConversationVC: openSettingsFromTitleView() } + // Handle taps outside of tableview cell to dismiss keyboard + @MainActor @objc func dismissKeyboardOnTap() { + _ = self.snInputView.resignFirstResponder() + } + @MainActor func openSettingsFromTitleView() { // If we shouldn't be able to access settings then disable the title view shortcuts guard viewModel.threadData.canAccessSettings(using: viewModel.dependencies) else { return } @@ -254,6 +260,11 @@ extension ConversationVC: return true } + + // MARK: - UIGestureRecognizerDelegate + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } // MARK: - SendMediaNavDelegate @@ -1063,7 +1074,7 @@ extension ConversationVC: } // MARK: MessageCellDelegate - + func handleItemLongPressed(_ cellViewModel: MessageViewModel) { // Show the unblock modal if needed guard self.viewModel.threadData.threadIsBlocked != true else { diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index fd52a451dc..0b13d35bbd 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -386,6 +386,16 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa return result }() + + // Handle taps outside of tableview cell + private lazy var tableViewTapGesture: UITapGestureRecognizer = { + let result: UITapGestureRecognizer = UITapGestureRecognizer() + result.delegate = self + result.addTarget(self, action: #selector(dismissKeyboardOnTap)) + result.cancelsTouchesInView = false + + return result + }() // MARK: - Settings @@ -537,6 +547,9 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa object: nil ) } + + // Gesture + view.addGestureRecognizer(tableViewTapGesture) self.viewModel.navigatableState.setupBindings(viewController: self, disposables: &self.viewModel.disposables) @@ -1580,7 +1593,8 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa // value will break things) let tableViewBottom: CGFloat = (tableView.contentSize.height - tableView.bounds.height + tableView.contentInset.bottom) - if tableView.contentOffset.y < (tableViewBottom - 5) { + // Added `insetDifference > 0` to remove sudden table collapse and overscroll + if tableView.contentOffset.y < (tableViewBottom - 5) && insetDifference > 0 { tableView.contentOffset.y += insetDifference } diff --git a/Session/Conversations/Input View/InputView.swift b/Session/Conversations/Input View/InputView.swift index 74316f9525..b75531a7e0 100644 --- a/Session/Conversations/Input View/InputView.swift +++ b/Session/Conversations/Input View/InputView.swift @@ -62,6 +62,15 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M return result }() + private lazy var swipeGestureRecognizer: UISwipeGestureRecognizer = { + let result: UISwipeGestureRecognizer = UISwipeGestureRecognizer() + result.direction = .down + result.addTarget(self, action: #selector(didSwipeDown)) + result.cancelsTouchesInView = false + + return result + }() + private var bottomStackView: UIStackView? private lazy var attachmentsButton: ExpandingAttachmentsButton = { let result = ExpandingAttachmentsButton(delegate: delegate) @@ -227,6 +236,7 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M autoresizingMask = .flexibleHeight addGestureRecognizer(tapGestureRecognizer) + addGestureRecognizer(swipeGestureRecognizer) // Background & blur let backgroundView = UIView() @@ -454,6 +464,7 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M self.accessibilityIdentifier = updatedInputState.accessibility?.identifier self.accessibilityLabel = updatedInputState.accessibility?.label tapGestureRecognizer.isEnabled = (updatedInputState.allowedInputTypes == .none) + inputState = updatedInputState disabledInputLabel.text = (updatedInputState.message ?? "") disabledInputLabel.accessibilityIdentifier = updatedInputState.messageAccessibility?.identifier @@ -630,6 +641,10 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M @objc private func characterLimitLabelTapped() { delegate?.handleCharacterLimitLabelTapped() } + + @objc private func didSwipeDown() { + inputTextView.resignFirstResponder() + } // MARK: - Convenience diff --git a/Session/Conversations/Message Cells/CallMessageCell.swift b/Session/Conversations/Message Cells/CallMessageCell.swift index de80fef7c1..47dca8d33b 100644 --- a/Session/Conversations/Message Cells/CallMessageCell.swift +++ b/Session/Conversations/Message Cells/CallMessageCell.swift @@ -18,6 +18,13 @@ final class CallMessageCell: MessageCell { override var contextSnapshotView: UIView? { return container } + override var allowedGestureRecognizers: Set { + return [ + .longPress, + .tap + ] + } + // MARK: - UI private lazy var topConstraint: NSLayoutConstraint = mainStackView.pin(.top, to: .top, of: self, withInset: CallMessageCell.inset) @@ -115,15 +122,6 @@ final class CallMessageCell: MessageCell { mainStackView.pin(.bottom, to: .bottom, of: self, withInset: -CallMessageCell.inset) } - override func setUpGestureRecognizers() { - let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) - addGestureRecognizer(longPressRecognizer) - - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) - tapGestureRecognizer.numberOfTapsRequired = 1 - addGestureRecognizer(tapGestureRecognizer) - } - // MARK: - Updating override func update( @@ -207,7 +205,7 @@ final class CallMessageCell: MessageCell { // MARK: - Interaction - @objc func handleLongPress(_ gestureRecognizer: UITapGestureRecognizer) { + override func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { if [ .ended, .cancelled, .failed ].contains(gestureRecognizer.state) { isHandlingLongPress = false return @@ -218,7 +216,7 @@ final class CallMessageCell: MessageCell { isHandlingLongPress = true } - @objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { + override func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { guard let dependencies: Dependencies = self.dependencies, let cellViewModel: MessageViewModel = self.viewModel, diff --git a/Session/Conversations/Message Cells/InfoMessageCell.swift b/Session/Conversations/Message Cells/InfoMessageCell.swift index 9c626a90c5..a5196a0b88 100644 --- a/Session/Conversations/Message Cells/InfoMessageCell.swift +++ b/Session/Conversations/Message Cells/InfoMessageCell.swift @@ -13,6 +13,13 @@ final class InfoMessageCell: MessageCell { override var contextSnapshotView: UIView? { return label } + override var allowedGestureRecognizers: Set { + return [ + .longPress, + .tap + ] + } + // MARK: - UI private lazy var iconContainerViewWidthConstraint = iconContainerView.set(.width, to: InfoMessageCell.iconSize) @@ -77,15 +84,6 @@ final class InfoMessageCell: MessageCell { stackView.pin(.right, to: .right, of: self, withInset: -Values.massiveSpacing) stackView.pin(.bottom, to: .bottom, of: self, withInset: -InfoMessageCell.inset) } - - override func setUpGestureRecognizers() { - let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) - addGestureRecognizer(longPressRecognizer) - - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) - tapGestureRecognizer.numberOfTapsRequired = 1 - addGestureRecognizer(tapGestureRecognizer) - } // MARK: - Updating @@ -169,7 +167,7 @@ final class InfoMessageCell: MessageCell { // MARK: - Interaction - @objc func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { + override func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { if [ .ended, .cancelled, .failed ].contains(gestureRecognizer.state) { isHandlingLongPress = false return @@ -180,9 +178,9 @@ final class InfoMessageCell: MessageCell { isHandlingLongPress = true } - @objc func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { + override func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { guard let cellViewModel: MessageViewModel = self.viewModel else { return } - + if cellViewModel.variant == .infoDisappearingMessagesUpdate && cellViewModel.canDoFollowingSetting() { delegate?.handleItemTapped(cellViewModel, cell: self, cellLocation: gestureRecognizer.location(in: self)) } diff --git a/Session/Conversations/Message Cells/MessageCell.swift b/Session/Conversations/Message Cells/MessageCell.swift index 39730a9d6c..ec40f8e72e 100644 --- a/Session/Conversations/Message Cells/MessageCell.swift +++ b/Session/Conversations/Message Cells/MessageCell.swift @@ -10,11 +10,16 @@ public enum SwipeState { case cancelled } +public enum GestureRecognizerType { + case tap, longPress, doubleTap +} + public class MessageCell: UITableViewCell { var dependencies: Dependencies? var viewModel: MessageViewModel? weak var delegate: MessageCellDelegate? open var contextSnapshotView: UIView? { return nil } + open var allowedGestureRecognizers: Set { return [] } // Override to have gestures // MARK: - Lifecycle @@ -41,7 +46,32 @@ public class MessageCell: UITableViewCell { } func setUpGestureRecognizers() { - // To be overridden by subclasses + var tapGestureRecognizer: UITapGestureRecognizer? + var doubleTapGestureRecognizer: UITapGestureRecognizer? + + if allowedGestureRecognizers.contains(.tap) { + let tapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) + tapGesture.numberOfTapsRequired = 1 + addGestureRecognizer(tapGesture) + tapGestureRecognizer = tapGesture + } + + if allowedGestureRecognizers.contains(.doubleTap) { + let doubleTapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap)) + doubleTapGesture.numberOfTapsRequired = 2 + addGestureRecognizer(doubleTapGesture) + doubleTapGestureRecognizer = doubleTapGesture + } + + if allowedGestureRecognizers.contains(.longPress) { + let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) + addGestureRecognizer(longPressGesture) + } + + // If we have both tap and double tap gestures then the single tap should fail if a double tap occurs + if let tapGesture: UITapGestureRecognizer = tapGestureRecognizer, let doubleTapGesture: UITapGestureRecognizer = doubleTapGestureRecognizer { + tapGesture.require(toFail: doubleTapGesture) + } } // MARK: - Updating @@ -93,6 +123,16 @@ public class MessageCell: UITableViewCell { return CallMessageCell.self } } + + // MARK: - Gesture events + @objc + func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {} + + @objc + func handleTap(_ gestureRecognizer: UITapGestureRecognizer) {} + + @objc + func handleDoubleTap() {} } // MARK: - MessageCellDelegate diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 21a9517955..0dbde5e460 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -25,6 +25,14 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { override var contextSnapshotView: UIView? { return snContentView } + override var allowedGestureRecognizers: Set { + return [ + .tap, + .longPress, + .doubleTap + ] + } + // Constraints internal lazy var authorLabelTopConstraint = authorLabel.pin(.top, to: .top, of: self) private lazy var authorLabelHeightConstraint = authorLabel.set(.height, to: 0) @@ -270,21 +278,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { messageStatusLabelPaddingView.pin(.leading, to: .leading, of: messageStatusContainerView) messageStatusLabelPaddingView.pin(.trailing, to: .trailing, of: messageStatusContainerView) } - - override func setUpGestureRecognizers() { - let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) - addGestureRecognizer(longPressRecognizer) - - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) - tapGestureRecognizer.numberOfTapsRequired = 1 - addGestureRecognizer(tapGestureRecognizer) - - let doubleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap)) - doubleTapGestureRecognizer.numberOfTapsRequired = 2 - addGestureRecognizer(doubleTapGestureRecognizer) - tapGestureRecognizer.require(toFail: doubleTapGestureRecognizer) - } - + // MARK: - Updating override func update( @@ -968,7 +962,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { } } - @objc func handleLongPress(_ gestureRecognizer: UITapGestureRecognizer) { + override func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { if [ .ended, .cancelled, .failed ].contains(gestureRecognizer.state) { isHandlingLongPress = false return @@ -994,9 +988,9 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { isHandlingLongPress = true } - @objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { + override func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { guard let cellViewModel: MessageViewModel = self.viewModel else { return } - + let location = gestureRecognizer.location(in: self) if profilePictureView.bounds.contains(profilePictureView.convert(location, from: self)), cellViewModel.shouldShowProfile { @@ -1062,7 +1056,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { } } - @objc private func handleDoubleTap() { + override func handleDoubleTap() { guard let cellViewModel: MessageViewModel = self.viewModel else { return } delegate?.handleItemDoubleTapped(cellViewModel)