From e079f7ba13d834aad3ed35e84e5b9ec39789a81f Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 3 Mar 2020 10:00:21 +1100 Subject: [PATCH] Allow user to drag beyond new conversation buttons --- .../Components/NewConversationButtonSet.swift | 86 +++++++++++++------ 1 file changed, 62 insertions(+), 24 deletions(-) diff --git a/Signal/src/Loki/Components/NewConversationButtonSet.swift b/Signal/src/Loki/Components/NewConversationButtonSet.swift index 39fcd0e69..fc5db863a 100644 --- a/Signal/src/Loki/Components/NewConversationButtonSet.swift +++ b/Signal/src/Loki/Components/NewConversationButtonSet.swift @@ -13,8 +13,8 @@ final class NewConversationButtonSet : UIView { // MARK: Components private lazy var mainButton = NewConversationButton(isMainButton: true, icon: #imageLiteral(resourceName: "Plus").scaled(to: CGSize(width: iconSize, height: iconSize))) - private lazy var newPrivateChatButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Message").scaled(to: CGSize(width: iconSize, height: iconSize))) - private lazy var newClosedGroupButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Group").scaled(to: CGSize(width: iconSize, height: iconSize))) + private lazy var createNewPrivateChatButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Message").scaled(to: CGSize(width: iconSize, height: iconSize))) + private lazy var createNewClosedGroupButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Group").scaled(to: CGSize(width: iconSize, height: iconSize))) private lazy var joinOpenGroupButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Globe").scaled(to: CGSize(width: iconSize, height: iconSize))) // MARK: Initialization @@ -33,12 +33,12 @@ final class NewConversationButtonSet : UIView { addSubview(joinOpenGroupButton) horizontalButtonConstraints[joinOpenGroupButton] = joinOpenGroupButton.pin(.left, to: .left, of: self, withInset: inset) verticalButtonConstraints[joinOpenGroupButton] = joinOpenGroupButton.pin(.bottom, to: .bottom, of: self, withInset: -inset) - addSubview(newPrivateChatButton) - newPrivateChatButton.center(.horizontal, in: self) - verticalButtonConstraints[newPrivateChatButton] = newPrivateChatButton.pin(.top, to: .top, of: self, withInset: inset) - addSubview(newClosedGroupButton) - horizontalButtonConstraints[newClosedGroupButton] = newClosedGroupButton.pin(.right, to: .right, of: self, withInset: -inset) - verticalButtonConstraints[newClosedGroupButton] = newClosedGroupButton.pin(.bottom, to: .bottom, of: self, withInset: -inset) + addSubview(createNewPrivateChatButton) + createNewPrivateChatButton.center(.horizontal, in: self) + verticalButtonConstraints[createNewPrivateChatButton] = createNewPrivateChatButton.pin(.top, to: .top, of: self, withInset: inset) + addSubview(createNewClosedGroupButton) + horizontalButtonConstraints[createNewClosedGroupButton] = createNewClosedGroupButton.pin(.right, to: .right, of: self, withInset: -inset) + verticalButtonConstraints[createNewClosedGroupButton] = createNewClosedGroupButton.pin(.bottom, to: .bottom, of: self, withInset: -inset) addSubview(mainButton) mainButton.center(.horizontal, in: self) mainButton.pin(.bottom, to: .bottom, of: self) @@ -53,9 +53,9 @@ final class NewConversationButtonSet : UIView { let joinOpenGroupButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleJoinOpenGroupButtonTapped)) joinOpenGroupButton.addGestureRecognizer(joinOpenGroupButtonTapGestureRecognizer) let createNewPrivateChatButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleCreateNewPrivateChatButtonTapped)) - newPrivateChatButton.addGestureRecognizer(createNewPrivateChatButtonTapGestureRecognizer) + createNewPrivateChatButton.addGestureRecognizer(createNewPrivateChatButtonTapGestureRecognizer) let createNewClosedGroupButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleCreateNewClosedGroupButtonTapped)) - newClosedGroupButton.addGestureRecognizer(createNewClosedGroupButtonTapGestureRecognizer) + createNewClosedGroupButton.addGestureRecognizer(createNewClosedGroupButtonTapGestureRecognizer) } // MARK: Interaction @@ -65,14 +65,14 @@ final class NewConversationButtonSet : UIView { @objc private func handleCreateNewClosedGroupButtonTapped() { delegate?.createNewClosedGroup() } private func expand(isUserDragging: Bool) { - let buttons = [ joinOpenGroupButton, newPrivateChatButton, newClosedGroupButton ] + let buttons = [ joinOpenGroupButton, createNewPrivateChatButton, createNewClosedGroupButton ] UIView.animate(withDuration: 0.25, animations: { buttons.forEach { $0.alpha = 1 } let inset = (Values.newConversationButtonExpandedSize - Values.newConversationButtonCollapsedSize) / 2 let size = Values.newConversationButtonCollapsedSize self.joinOpenGroupButton.frame = CGRect(origin: CGPoint(x: inset, y: self.height() - size - inset), size: CGSize(width: size, height: size)) - self.newPrivateChatButton.frame = CGRect(center: CGPoint(x: self.bounds.center.x, y: inset + size / 2), size: CGSize(width: size, height: size)) - self.newClosedGroupButton.frame = CGRect(origin: CGPoint(x: self.width() - size - inset, y: self.height() - size - inset), size: CGSize(width: size, height: size)) + self.createNewPrivateChatButton.frame = CGRect(center: CGPoint(x: self.bounds.center.x, y: inset + size / 2), size: CGSize(width: size, height: size)) + self.createNewClosedGroupButton.frame = CGRect(origin: CGPoint(x: self.width() - size - inset, y: self.height() - size - inset), size: CGSize(width: size, height: size)) }, completion: { _ in self.isUserDragging = isUserDragging }) @@ -80,7 +80,7 @@ final class NewConversationButtonSet : UIView { private func collapse(withAnimation isAnimated: Bool) { isUserDragging = false - let buttons = [ joinOpenGroupButton, newPrivateChatButton, newClosedGroupButton ] + let buttons = [ joinOpenGroupButton, createNewPrivateChatButton, createNewClosedGroupButton ] UIView.animate(withDuration: isAnimated ? 0.25 : 0) { buttons.forEach { button in button.alpha = 0 @@ -117,8 +117,14 @@ final class NewConversationButtonSet : UIView { let touchLocationInSelfCoordinates = touch.location(in: self) mainButton.frame = CGRect(center: touchLocationInSelfCoordinates, size: mainButtonSize) mainButton.alpha = 1 - (touchLocationInSelfCoordinates.distance(to: mainButtonLocationInSelfCoordinates) / maxDragDistance) - let buttons = [ joinOpenGroupButton, newPrivateChatButton, newClosedGroupButton ] - let buttonToExpand = buttons.first { $0.contains(touch) } + let buttons = [ joinOpenGroupButton, createNewPrivateChatButton, createNewClosedGroupButton ] + let buttonToExpand = buttons.first { button in + var hasUserDraggedBeyondButton = false + if button == joinOpenGroupButton && touch.isLeft(of: joinOpenGroupButton) { hasUserDraggedBeyondButton = true } + if button == createNewPrivateChatButton && touch.isAbove(createNewPrivateChatButton) { hasUserDraggedBeyondButton = true } + if button == createNewClosedGroupButton && touch.isRight(of: createNewClosedGroupButton) { hasUserDraggedBeyondButton = true } + return button.contains(touch) || hasUserDraggedBeyondButton + } if let buttonToExpand = buttonToExpand { guard buttonToExpand != expandedButton else { return } if let expandedButton = expandedButton { collapse(expandedButton) } @@ -132,9 +138,9 @@ final class NewConversationButtonSet : UIView { override func touchesEnded(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first, isUserDragging else { return } - if joinOpenGroupButton.contains(touch) { delegate?.joinOpenGroup() } - else if newPrivateChatButton.contains(touch) { delegate?.createNewPrivateChat() } - else if newClosedGroupButton.contains(touch) { delegate?.createNewClosedGroup() } + if joinOpenGroupButton.contains(touch) || touch.isLeft(of: joinOpenGroupButton) { delegate?.joinOpenGroup() } + else if createNewPrivateChatButton.contains(touch) || touch.isAbove(createNewPrivateChatButton) { delegate?.createNewPrivateChat() } + else if createNewClosedGroupButton.contains(touch) || touch.isRight(of: createNewClosedGroupButton) { delegate?.createNewClosedGroup() } reset() } @@ -164,11 +170,11 @@ final class NewConversationButtonSet : UIView { if joinOpenGroupButton == expandedButton { horizontalButtonConstraints[joinOpenGroupButton]!.constant = inset verticalButtonConstraints[joinOpenGroupButton]!.constant = -inset - } else if newPrivateChatButton == expandedButton { - verticalButtonConstraints[newPrivateChatButton]!.constant = inset - } else if newClosedGroupButton == expandedButton { - horizontalButtonConstraints[newClosedGroupButton]!.constant = -inset - verticalButtonConstraints[newClosedGroupButton]!.constant = -inset + } else if createNewPrivateChatButton == expandedButton { + verticalButtonConstraints[createNewPrivateChatButton]!.constant = inset + } else if createNewClosedGroupButton == expandedButton { + horizontalButtonConstraints[createNewClosedGroupButton]!.constant = -inset + verticalButtonConstraints[createNewClosedGroupButton]!.constant = -inset } let size = Values.newConversationButtonCollapsedSize let frame = CGRect(center: button.center, size: CGSize(width: size, height: size)) @@ -182,6 +188,11 @@ final class NewConversationButtonSet : UIView { button.backgroundColor = Colors.newConversationButtonCollapsedBackground } } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !bounds.contains(point), isUserDragging { collapse(withAnimation: true) } + return super.hitTest(point, with: event) + } } // MARK: Delegate @@ -253,6 +264,33 @@ private extension UIView { } } +private extension UITouch { + + func isLeft(of view: UIView) -> Bool { + return isContainedVertically(in: view) && location(in: view).x < view.bounds.minX + } + + func isAbove(_ view: UIView) -> Bool { + return isContainedHorizontally(in: view) && location(in: view).y < view.bounds.minY + } + + func isRight(of view: UIView) -> Bool { + return isContainedVertically(in: view) && location(in: view).x > view.bounds.maxX + } + + func isBelow(_ view: UIView) -> Bool { + return isContainedHorizontally(in: view) && location(in: view).y > view.bounds.maxY + } + + private func isContainedHorizontally(in view: UIView) -> Bool { + return (view.bounds.minX...view.bounds.maxX) ~= location(in: view).x + } + + private func isContainedVertically(in view: UIView) -> Bool { + return (view.bounds.minY...view.bounds.maxY) ~= location(in: view).y + } +} + private extension CGPoint { func distance(to otherPoint: CGPoint) -> CGFloat {