diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index bfb2637c1..fe650ab1e 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -138,6 +138,7 @@ 7B7CB192271508AD0079FF93 /* CallRingTonePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB191271508AD0079FF93 /* CallRingTonePlayer.swift */; }; 7B81682328A4C1210069F315 /* UpdateTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682228A4C1210069F315 /* UpdateTypes.swift */; }; 7B81682828B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */; }; + 7B8C44C528B49DDA00FBE25F /* NewConversationVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8C44C428B49DDA00FBE25F /* NewConversationVC.swift */; }; 7B8D5FC428332600008324D9 /* VisibleMessage+Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */; }; 7B93D06A27CF173D00811CB6 /* MessageRequestsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D06927CF173D00811CB6 /* MessageRequestsViewController.swift */; }; 7B93D07027CF194000811CB6 /* ConfigurationMessage+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */; }; @@ -321,7 +322,6 @@ C32C5E0C256DDAFA003C73A2 /* NSRegularExpression+SSK.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA7A255A57FB00E217F9 /* NSRegularExpression+SSK.swift */; }; C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB77255A581000E217F9 /* NSUserDefaults+OWS.m */; }; C32C6018256E07F9003C73A2 /* NSUserDefaults+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB51255A580D00E217F9 /* NSUserDefaults+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C33100082558FF6D00070591 /* NewConversationButtonSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */; }; C33100092558FF6D00070591 /* UserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31D1DDC25217014005D4DA8 /* UserCell.swift */; }; C33100142558FFC200070591 /* UIImage+Tinting.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33100132558FFC200070591 /* UIImage+Tinting.swift */; }; C33100282559000A00070591 /* UIView+Rendering.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33100272559000A00070591 /* UIView+Rendering.swift */; }; @@ -1178,6 +1178,7 @@ 7B7CB191271508AD0079FF93 /* CallRingTonePlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallRingTonePlayer.swift; sourceTree = ""; }; 7B81682228A4C1210069F315 /* UpdateTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateTypes.swift; sourceTree = ""; }; 7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _007_HomeQueryOptimisationIndexes.swift; sourceTree = ""; }; + 7B8C44C428B49DDA00FBE25F /* NewConversationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationVC.swift; sourceTree = ""; }; 7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+Reaction.swift"; sourceTree = ""; }; 7B93D06927CF173D00811CB6 /* MessageRequestsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewController.swift; sourceTree = ""; }; 7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = ""; }; @@ -1264,7 +1265,6 @@ B835247825C38D880089A44F /* MessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCell.swift; sourceTree = ""; }; B835249A25C3AB650089A44F /* VisibleMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleMessageCell.swift; sourceTree = ""; }; B83524A425C3BA4B0089A44F /* InfoMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoMessageCell.swift; sourceTree = ""; }; - B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationButtonSet.swift; sourceTree = ""; }; B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Scaling.swift"; sourceTree = ""; }; B84664F4235022F30083A1CD /* MentionUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionUtilities.swift; sourceTree = ""; }; B847570023D568EB00759540 /* SignalServiceKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SignalServiceKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -2203,6 +2203,15 @@ name = "Recovered References"; sourceTree = ""; }; + 7B8C44C328B49DA900FBE25F /* New Conversation */ = { + isa = PBXGroup; + children = ( + B8CCF63623961D6D0091D419 /* NewDMVC.swift */, + 7B8C44C428B49DDA00FBE25F /* NewConversationVC.swift */, + ); + path = "New Conversation"; + sourceTree = ""; + }; 7B93D06827CF173D00811CB6 /* Message Requests */ = { isa = PBXGroup; children = ( @@ -2844,11 +2853,11 @@ C360968E25AD16E8008B62B2 /* Home */ = { isa = PBXGroup; children = ( + 7B8C44C328B49DA900FBE25F /* New Conversation */, 7B93D06827CF173D00811CB6 /* Message Requests */, 7BAF54CA27ACCEEC003D12F8 /* GlobalSearch */, FDCDB8DF2811007F00352A0C /* HomeViewModel.swift */, B8BB82A4238F627000BA5194 /* HomeVC.swift */, - B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */, ); path = Home; sourceTree = ""; @@ -2902,14 +2911,6 @@ path = "Closed Groups"; sourceTree = ""; }; - C36096A525AD18D7008B62B2 /* DMs */ = { - isa = PBXGroup; - children = ( - B8CCF63623961D6D0091D419 /* NewDMVC.swift */, - ); - path = DMs; - sourceTree = ""; - }; C36096AF25AD1932008B62B2 /* Sheets & Modals */ = { isa = PBXGroup; children = ( @@ -3476,7 +3477,6 @@ C360969C25AD18BA008B62B2 /* Closed Groups */, B835246C25C38AA20089A44F /* Conversations */, C32B405424A961E1001117B5 /* Dependencies */, - C36096A525AD18D7008B62B2 /* DMs */, C360968E25AD16E8008B62B2 /* Home */, C36096BA25AD1B14008B62B2 /* Media Viewing & Editing */, C36096BB25AD1BBB008B62B2 /* Notifications */, @@ -5402,6 +5402,7 @@ 7B7CB18B270591630079FF93 /* ShareLogsModal.swift in Sources */, 4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */, 3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */, + 7B8C44C528B49DDA00FBE25F /* NewConversationVC.swift in Sources */, C3A76A8D25DB83F90074CB90 /* PermissionMissingModal.swift in Sources */, 340FC8A9204DAC8D007AEB0F /* NotificationSettingsOptionsViewController.m in Sources */, 7B1B52E028580D51006069F2 /* EmojiSkinTonePicker.swift in Sources */, @@ -5510,7 +5511,6 @@ 340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */, 7B0EFDF0275084AA00FFAAE7 /* CallMessageCell.swift in Sources */, 7B93D06A27CF173D00811CB6 /* MessageRequestsViewController.swift in Sources */, - C33100082558FF6D00070591 /* NewConversationButtonSet.swift in Sources */, C3AAFFF225AE99710089E6DD /* AppDelegate.swift in Sources */, B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */, C31A6C5A247F214E001123EF /* UIView+Glow.swift in Sources */, diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 2250707c7..3bbad236b 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -9,7 +9,7 @@ import SignalUtilitiesKit final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedReminderViewDelegate { private static let loadingHeaderHeight: CGFloat = 20 - private static let newConversationButtonSize: CGFloat = 60 + public static let newConversationButtonSize: CGFloat = 60 private let viewModel: HomeViewModel = HomeViewModel() private var dataChangeObservable: DatabaseCancellable? @@ -73,9 +73,8 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi left: 0, bottom: ( Values.newConversationButtonBottomOffset + - NewConversationButtonSet.expandedButtonSize + Values.largeSpacing + - NewConversationButtonSet.collapsedButtonSize + HomeVC.newConversationButtonSize ), right: 0 ) @@ -749,7 +748,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi if UIDevice.current.isIPad { navigationController.modalPresentationStyle = .fullScreen } - navigationController.modalPresentationCapturesStatusBarAppearance = true + navigationController.modalPresentationCapturesStatusBarAppearance = true present(navigationController, animated: true, completion: nil) } diff --git a/Session/Home/Message Requests/MessageRequestsViewController.swift b/Session/Home/Message Requests/MessageRequestsViewController.swift index ba87a80c3..0303f04a2 100644 --- a/Session/Home/Message Requests/MessageRequestsViewController.swift +++ b/Session/Home/Message Requests/MessageRequestsViewController.swift @@ -44,7 +44,7 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat result.dataSource = self result.delegate = self - let bottomInset = Values.newConversationButtonBottomOffset + NewConversationButtonSet.expandedButtonSize + Values.largeSpacing + NewConversationButtonSet.collapsedButtonSize + let bottomInset = Values.newConversationButtonBottomOffset + Values.largeSpacing + HomeVC.newConversationButtonSize result.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bottomInset, right: 0) result.showsVerticalScrollIndicator = false @@ -180,7 +180,7 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat constant: -Values.largeSpacing ), clearAllButton.widthAnchor.constraint(equalToConstant: Values.iPadButtonWidth), - clearAllButton.heightAnchor.constraint(equalToConstant: NewConversationButtonSet.collapsedButtonSize) + clearAllButton.heightAnchor.constraint(equalToConstant: HomeVC.newConversationButtonSize) ]) } diff --git a/Session/Home/New Conversation/NewConversationVC.swift b/Session/Home/New Conversation/NewConversationVC.swift new file mode 100644 index 000000000..0a8ab0dac --- /dev/null +++ b/Session/Home/New Conversation/NewConversationVC.swift @@ -0,0 +1,3 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation diff --git a/Session/DMs/NewDMVC.swift b/Session/Home/New Conversation/NewDMVC.swift similarity index 100% rename from Session/DMs/NewDMVC.swift rename to Session/Home/New Conversation/NewDMVC.swift diff --git a/Session/Home/NewConversationButtonSet.swift b/Session/Home/NewConversationButtonSet.swift deleted file mode 100644 index b1f933479..000000000 --- a/Session/Home/NewConversationButtonSet.swift +++ /dev/null @@ -1,365 +0,0 @@ -import UIKit -import SessionUIKit - -final class NewConversationButtonSet : UIView { - private var isUserDragging = false - private var horizontalButtonConstraints: [NewConversationButton:NSLayoutConstraint] = [:] - private var verticalButtonConstraints: [NewConversationButton:NSLayoutConstraint] = [:] - private var expandedButton: NewConversationButton? - var delegate: NewConversationButtonSetDelegate? - - // MARK: Settings - private let spacing = Values.veryLargeSpacing - private let iconSize = CGFloat(24) - private let maxDragDistance = CGFloat(56) - private let dragMargin = CGFloat(16) - static let collapsedButtonSize = CGFloat(60) - static let expandedButtonSize = CGFloat(72) - - // MARK: Components - private lazy var mainButton = NewConversationButton(isMainButton: true, icon: #imageLiteral(resourceName: "Plus").scaled(to: CGSize(width: iconSize, height: iconSize))) - private lazy var newDMButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Message").scaled(to: CGSize(width: iconSize, height: iconSize))) - private lazy var createClosedGroupButton = 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))) - - private lazy var newDMLabel: UILabel = { - let result: UILabel = UILabel() - result.translatesAutoresizingMaskIntoConstraints = false - result.font = UIFont.systemFont(ofSize: Values.verySmallFontSize, weight: .bold) - result.text = NSLocalizedString("NEW_CONVERSATION_MENU_DIRECT_MESSAGE", comment: "").uppercased() - result.textColor = Colors.grey - result.textAlignment = .center - - return result - }() - - private lazy var createClosedGroupLabel: UILabel = { - let result: UILabel = UILabel() - result.translatesAutoresizingMaskIntoConstraints = false - result.font = UIFont.systemFont(ofSize: Values.verySmallFontSize, weight: .bold) - result.text = NSLocalizedString("NEW_CONVERSATION_MENU_CLOSED_GROUP", comment: "").uppercased() - result.textColor = Colors.grey - result.textAlignment = .center - - return result - }() - - private lazy var joinOpenGroupLabel: UILabel = { - let result: UILabel = UILabel() - result.translatesAutoresizingMaskIntoConstraints = false - result.font = UIFont.systemFont(ofSize: Values.verySmallFontSize, weight: .bold) - result.text = NSLocalizedString("NEW_CONVERSATION_MENU_OPEN_GROUP", comment: "").uppercased() - result.textColor = Colors.grey - result.textAlignment = .center - - return result - }() - - // MARK: Initialization - override init(frame: CGRect) { - super.init(frame: frame) - setUpViewHierarchy() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - setUpViewHierarchy() - } - - private func setUpViewHierarchy() { - mainButton.accessibilityLabel = "Toggle conversation options button" - mainButton.isAccessibilityElement = true - newDMButton.accessibilityLabel = "Start new one-on-one conversation button" - newDMButton.isAccessibilityElement = true - createClosedGroupButton.accessibilityLabel = "Start new closed group button" - createClosedGroupButton.isAccessibilityElement = true - joinOpenGroupButton.accessibilityLabel = "Join open group button" - joinOpenGroupButton.isAccessibilityElement = true - let inset = (NewConversationButtonSet.expandedButtonSize - NewConversationButtonSet.collapsedButtonSize) / 2 - addSubview(joinOpenGroupLabel) - addSubview(joinOpenGroupButton) - horizontalButtonConstraints[joinOpenGroupButton] = joinOpenGroupButton.pin(.left, to: .left, of: self, withInset: inset) - verticalButtonConstraints[joinOpenGroupButton] = joinOpenGroupButton.pin(.bottom, to: .bottom, of: self, withInset: -inset) - joinOpenGroupLabel.center(.horizontal, in: joinOpenGroupButton) - joinOpenGroupLabel.pin(.top, to: .bottom, of: joinOpenGroupButton, withInset: 8) - addSubview(newDMLabel) - addSubview(newDMButton) - newDMButton.center(.horizontal, in: self) - verticalButtonConstraints[newDMButton] = newDMButton.pin(.top, to: .top, of: self, withInset: inset) - newDMLabel.center(.horizontal, in: newDMButton) - newDMLabel.pin(.top, to: .bottom, of: newDMButton, withInset: 8) - addSubview(createClosedGroupLabel) - addSubview(createClosedGroupButton) - horizontalButtonConstraints[createClosedGroupButton] = createClosedGroupButton.pin(.right, to: .right, of: self, withInset: -inset) - verticalButtonConstraints[createClosedGroupButton] = createClosedGroupButton.pin(.bottom, to: .bottom, of: self, withInset: -inset) - createClosedGroupLabel.center(.horizontal, in: createClosedGroupButton) - createClosedGroupLabel.pin(.top, to: .bottom, of: createClosedGroupButton, withInset: 8) - addSubview(mainButton) - mainButton.center(.horizontal, in: self) - mainButton.pin(.bottom, to: .bottom, of: self, withInset: -inset) - let width = 2 * NewConversationButtonSet.expandedButtonSize + 2 * spacing + NewConversationButtonSet.collapsedButtonSize - set(.width, to: width) - let height = NewConversationButtonSet.expandedButtonSize + spacing + NewConversationButtonSet.collapsedButtonSize - set(.height, to: height) - collapse(withAnimation: false) - isUserInteractionEnabled = true - let joinOpenGroupButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleJoinOpenGroupButtonTapped)) - joinOpenGroupButton.addGestureRecognizer(joinOpenGroupButtonTapGestureRecognizer) - let createNewPrivateChatButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleCreateNewPrivateChatButtonTapped)) - newDMButton.addGestureRecognizer(createNewPrivateChatButtonTapGestureRecognizer) - let createNewClosedGroupButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleCreateNewClosedGroupButtonTapped)) - createClosedGroupButton.addGestureRecognizer(createNewClosedGroupButtonTapGestureRecognizer) - } - - // MARK: Interaction - @objc private func handleJoinOpenGroupButtonTapped() { delegate?.joinOpenGroup() } - @objc private func handleCreateNewPrivateChatButtonTapped() { delegate?.createNewDM() } - @objc private func handleCreateNewClosedGroupButtonTapped() { delegate?.createClosedGroup() } - - private func expand(isUserDragging: Bool) { - let views = [ joinOpenGroupButton, joinOpenGroupLabel, newDMButton, newDMLabel, createClosedGroupButton, createClosedGroupLabel ] - UIView.animate(withDuration: 0.25, animations: { - views.forEach { $0.alpha = 1 } - let inset = (NewConversationButtonSet.expandedButtonSize - NewConversationButtonSet.collapsedButtonSize) / 2 - let size = NewConversationButtonSet.collapsedButtonSize - self.joinOpenGroupButton.frame = CGRect(origin: CGPoint(x: inset, y: self.height() - size - inset), size: CGSize(width: size, height: size)) - self.joinOpenGroupLabel.center = CGPoint(x: self.joinOpenGroupButton.center.x, y: self.joinOpenGroupButton.frame.maxY + 8 + (self.joinOpenGroupLabel.bounds.height / 2)) - self.newDMButton.frame = CGRect(center: CGPoint(x: self.bounds.center.x, y: inset + size / 2), size: CGSize(width: size, height: size)) - self.newDMLabel.center = CGPoint(x: self.newDMButton.center.x, y: self.newDMButton.frame.maxY + 8 + (self.newDMLabel.bounds.height / 2)) - self.createClosedGroupButton.frame = CGRect(origin: CGPoint(x: self.width() - size - inset, y: self.height() - size - inset), size: CGSize(width: size, height: size)) - self.createClosedGroupLabel.center = CGPoint(x: self.createClosedGroupButton.center.x, y: self.createClosedGroupButton.frame.maxY + 8 + (self.createClosedGroupLabel.bounds.height / 2)) - }, completion: { _ in - self.isUserDragging = isUserDragging - }) - } - - private func collapse(withAnimation isAnimated: Bool) { - isUserDragging = false - let buttons = [ joinOpenGroupButton, newDMButton, createClosedGroupButton ] - let labels = [ joinOpenGroupLabel, newDMLabel, createClosedGroupLabel ] - UIView.animate(withDuration: isAnimated ? 0.25 : 0) { - labels.forEach { label in - label.alpha = 0 - label.center = self.mainButton.center - } - buttons.forEach { button in - button.alpha = 0 - let size = NewConversationButtonSet.collapsedButtonSize - button.frame = CGRect(center: self.mainButton.center, size: CGSize(width: size, height: size)) - } - } - } - - private func reset() { - let mainButtonLocationInSelfCoordinates = CGPoint(x: width() / 2, y: height() - NewConversationButtonSet.expandedButtonSize / 2) - let mainButtonSize = mainButton.frame.size - UIView.animate(withDuration: 0.25) { - self.mainButton.frame = CGRect(center: mainButtonLocationInSelfCoordinates, size: mainButtonSize) - self.mainButton.alpha = 1 - } - if let expandedButton = expandedButton { collapse(expandedButton) } - expandedButton = nil - Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { _ in - self.collapse(withAnimation: true) - } - } - - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - guard let touch = touches.first, mainButton.contains(touch), !isUserDragging else { return } - UIImpactFeedbackGenerator(style: .heavy).impactOccurred() - expand(isUserDragging: true) - } - - override func touchesMoved(_ touches: Set, with event: UIEvent?) { - guard let touch = touches.first, isUserDragging else { return } - let mainButtonSize = mainButton.frame.size - let mainButtonLocationInSelfCoordinates = CGPoint(x: width() / 2, y: height() - NewConversationButtonSet.expandedButtonSize / 2) - let touchLocationInSelfCoordinates = touch.location(in: self) - mainButton.frame = CGRect(center: touchLocationInSelfCoordinates, size: mainButtonSize) - mainButton.alpha = 1 - (touchLocationInSelfCoordinates.distance(to: mainButtonLocationInSelfCoordinates) / maxDragDistance) - let buttons = [ joinOpenGroupButton, newDMButton, createClosedGroupButton ] - let buttonToExpand = buttons.first { button in - var hasUserDraggedBeyondButton = false - if button == joinOpenGroupButton && touch.isLeft(of: joinOpenGroupButton, with: dragMargin) { hasUserDraggedBeyondButton = true } - if button == newDMButton && touch.isAbove(newDMButton, with: dragMargin) { hasUserDraggedBeyondButton = true } - if button == createClosedGroupButton && touch.isRight(of: createClosedGroupButton, with: dragMargin) { hasUserDraggedBeyondButton = true } - return button.contains(touch) || hasUserDraggedBeyondButton - } - if let buttonToExpand = buttonToExpand { - guard buttonToExpand != expandedButton else { return } - if let expandedButton = expandedButton { collapse(expandedButton) } - expand(buttonToExpand) - expandedButton = buttonToExpand - } else { - if let expandedButton = expandedButton { collapse(expandedButton) } - expandedButton = nil - } - } - - override func touchesEnded(_ touches: Set, with event: UIEvent?) { - guard let touch = touches.first, isUserDragging else { return } - if joinOpenGroupButton.contains(touch) || touch.isLeft(of: joinOpenGroupButton, with: dragMargin) { delegate?.joinOpenGroup() } - else if newDMButton.contains(touch) || touch.isAbove(newDMButton, with: dragMargin) { delegate?.createNewDM() } - else if createClosedGroupButton.contains(touch) || touch.isRight(of: createClosedGroupButton, with: dragMargin) { delegate?.createClosedGroup() } - reset() - } - - override func touchesCancelled(_ touches: Set, with event: UIEvent?) { - guard isUserDragging else { return } - reset() - } - - private func expand(_ button: NewConversationButton) { - if let horizontalConstraint = horizontalButtonConstraints[button] { horizontalConstraint.constant = 0 } - if let verticalConstraint = verticalButtonConstraints[button] { verticalConstraint.constant = 0 } - let size = NewConversationButtonSet.expandedButtonSize - let frame = CGRect(center: button.center, size: CGSize(width: size, height: size)) - button.widthConstraint.constant = size - button.heightConstraint.constant = size - UIView.animate(withDuration: 0.25) { - self.layoutIfNeeded() - button.frame = frame - button.layer.cornerRadius = size / 2 - let glowColor = Colors.expandedButtonGlowColor - let glowConfiguration = UIView.CircularGlowConfiguration(size: size, color: glowColor, isAnimated: true, radius: isLightMode ? 4 : 6) - button.setCircularGlow(with: glowConfiguration) - button.backgroundColor = Colors.accent - } - } - - private func collapse(_ button: NewConversationButton) { - let inset = (NewConversationButtonSet.expandedButtonSize - NewConversationButtonSet.collapsedButtonSize) / 2 - if joinOpenGroupButton == expandedButton { - horizontalButtonConstraints[joinOpenGroupButton]!.constant = inset - verticalButtonConstraints[joinOpenGroupButton]!.constant = -inset - } else if newDMButton == expandedButton { - verticalButtonConstraints[newDMButton]!.constant = inset - } else if createClosedGroupButton == expandedButton { - horizontalButtonConstraints[createClosedGroupButton]!.constant = -inset - verticalButtonConstraints[createClosedGroupButton]!.constant = -inset - } - let size = NewConversationButtonSet.collapsedButtonSize - let frame = CGRect(center: button.center, size: CGSize(width: size, height: size)) - button.widthConstraint.constant = size - button.heightConstraint.constant = size - UIView.animate(withDuration: 0.25) { - self.layoutIfNeeded() - button.frame = frame - button.layer.cornerRadius = size / 2 - let glowColor = isLightMode ? UIColor.black.withAlphaComponent(0.4) : UIColor.black - let glowConfiguration = UIView.CircularGlowConfiguration(size: size, color: glowColor, isAnimated: true, radius: isLightMode ? 4 : 6) - button.setCircularGlow(with: glowConfiguration) - button.backgroundColor = Colors.newConversationButtonCollapsedBackground - } - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - let allButtons = [ mainButton, joinOpenGroupButton, newDMButton, createClosedGroupButton ] - if allButtons.contains(where: { $0.frame.contains(point) }) { - return super.hitTest(point, with: event) - } else { - collapse(withAnimation: true) - return nil - } - } -} - -// MARK: Delegate -protocol NewConversationButtonSetDelegate { - - func joinOpenGroup() - func createNewDM() - func createClosedGroup() -} - -// MARK: Button -private final class NewConversationButton : UIImageView { - private let isMainButton: Bool - private let icon: UIImage - var widthConstraint: NSLayoutConstraint! - var heightConstraint: NSLayoutConstraint! - - init(isMainButton: Bool, icon: UIImage) { - self.isMainButton = isMainButton - self.icon = icon - super.init(frame: CGRect.zero) - setUpViewHierarchy() - NotificationCenter.default.addObserver(self, selector: #selector(handleAppModeChangedNotification(_:)), name: .appModeChanged, object: nil) - } - - override init(frame: CGRect) { - preconditionFailure("Use init(isMainButton:) instead.") - } - - required init?(coder: NSCoder) { - preconditionFailure("Use init(isMainButton:) instead.") - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - private func setUpViewHierarchy(isUpdate: Bool = false) { - let newConversationButtonCollapsedBackground = isLightMode ? UIColor(hex: 0xF5F5F5) : UIColor(hex: 0x1F1F1F) - backgroundColor = isMainButton ? Colors.accent : newConversationButtonCollapsedBackground - let size = NewConversationButtonSet.collapsedButtonSize - layer.cornerRadius = size / 2 - let glowColor = isMainButton ? Colors.expandedButtonGlowColor : (isLightMode ? UIColor.black.withAlphaComponent(0.4) : UIColor.black) - let glowConfiguration = UIView.CircularGlowConfiguration(size: size, color: glowColor, isAnimated: false, radius: isLightMode ? 4 : 6) - setCircularGlow(with: glowConfiguration) - layer.masksToBounds = false - let iconColor = (isMainButton && isLightMode) ? UIColor.white : (isLightMode ? UIColor.black : UIColor.white) - image = icon.asTintedImage(color: iconColor)! - contentMode = .center - if !isUpdate { - widthConstraint = set(.width, to: size) - heightConstraint = set(.height, to: size) - } - } - - @objc private func handleAppModeChangedNotification(_ notification: Notification) { - setUpViewHierarchy(isUpdate: true) - } -} - -// MARK: Convenience -private extension UIView { - - func contains(_ touch: UITouch) -> Bool { - return bounds.contains(touch.location(in: self)) - } -} - -private extension UITouch { - - func isLeft(of view: UIView, with margin: CGFloat = 0) -> Bool { - return isContainedVertically(in: view, with: margin) && location(in: view).x < view.bounds.minX - } - - func isAbove(_ view: UIView, with margin: CGFloat = 0) -> Bool { - return isContainedHorizontally(in: view, with: margin) && location(in: view).y < view.bounds.minY - } - - func isRight(of view: UIView, with margin: CGFloat = 0) -> Bool { - return isContainedVertically(in: view, with: margin) && location(in: view).x > view.bounds.maxX - } - - func isBelow(_ view: UIView, with margin: CGFloat = 0) -> Bool { - return isContainedHorizontally(in: view, with: margin) && location(in: view).y > view.bounds.maxY - } - - private func isContainedHorizontally(in view: UIView, with margin: CGFloat = 0) -> Bool { - return ((view.bounds.minX - margin)...(view.bounds.maxX + margin)) ~= location(in: view).x - } - - private func isContainedVertically(in view: UIView, with margin: CGFloat = 0) -> Bool { - return ((view.bounds.minY - margin)...(view.bounds.maxY + margin)) ~= location(in: view).y - } -} - -private extension CGPoint { - - func distance(to otherPoint: CGPoint) -> CGFloat { - return sqrt(pow(self.x - otherPoint.x, 2) + pow(self.y - otherPoint.y, 2)) - } -}