This commit is contained in:
ryanzhao 2022-08-23 15:41:51 +10:00
parent ae5d0b0024
commit 3d24c1526e
6 changed files with 21 additions and 384 deletions

View File

@ -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 = "<group>"; };
7B81682228A4C1210069F315 /* UpdateTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateTypes.swift; sourceTree = "<group>"; };
7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _007_HomeQueryOptimisationIndexes.swift; sourceTree = "<group>"; };
7B8C44C428B49DDA00FBE25F /* NewConversationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationVC.swift; sourceTree = "<group>"; };
7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+Reaction.swift"; sourceTree = "<group>"; };
7B93D06927CF173D00811CB6 /* MessageRequestsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewController.swift; sourceTree = "<group>"; };
7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = "<group>"; };
@ -1264,7 +1265,6 @@
B835247825C38D880089A44F /* MessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCell.swift; sourceTree = "<group>"; };
B835249A25C3AB650089A44F /* VisibleMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleMessageCell.swift; sourceTree = "<group>"; };
B83524A425C3BA4B0089A44F /* InfoMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoMessageCell.swift; sourceTree = "<group>"; };
B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationButtonSet.swift; sourceTree = "<group>"; };
B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Scaling.swift"; sourceTree = "<group>"; };
B84664F4235022F30083A1CD /* MentionUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionUtilities.swift; sourceTree = "<group>"; };
B847570023D568EB00759540 /* SignalServiceKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SignalServiceKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -2203,6 +2203,15 @@
name = "Recovered References";
sourceTree = "<group>";
};
7B8C44C328B49DA900FBE25F /* New Conversation */ = {
isa = PBXGroup;
children = (
B8CCF63623961D6D0091D419 /* NewDMVC.swift */,
7B8C44C428B49DDA00FBE25F /* NewConversationVC.swift */,
);
path = "New Conversation";
sourceTree = "<group>";
};
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 = "<group>";
@ -2902,14 +2911,6 @@
path = "Closed Groups";
sourceTree = "<group>";
};
C36096A525AD18D7008B62B2 /* DMs */ = {
isa = PBXGroup;
children = (
B8CCF63623961D6D0091D419 /* NewDMVC.swift */,
);
path = DMs;
sourceTree = "<group>";
};
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 */,

View File

@ -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)
}

View File

@ -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)
])
}

View File

@ -0,0 +1,3 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation

View File

@ -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<UITouch>, 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<UITouch>, 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<UITouch>, 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<UITouch>, 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))
}
}