mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
189 lines
7.8 KiB
Swift
189 lines
7.8 KiB
Swift
|
|
final class ContextMenuVC : UIViewController {
|
|
private let snapshot: UIView
|
|
private let viewItem: ConversationViewItem
|
|
private let frame: CGRect
|
|
private let dismiss: () -> Void
|
|
private weak var delegate: ContextMenuActionDelegate?
|
|
|
|
// MARK: UI Components
|
|
private lazy var blurView = UIVisualEffectView(effect: nil)
|
|
|
|
private lazy var emojiBar: UIView = {
|
|
let result = UIView()
|
|
result.layer.shadowColor = UIColor.black.cgColor
|
|
result.layer.shadowOffset = CGSize.zero
|
|
result.layer.shadowOpacity = 0.4
|
|
result.layer.shadowRadius = 4
|
|
result.set(.height, to: ContextMenuVC.actionViewHeight)
|
|
return result
|
|
}()
|
|
|
|
private lazy var emojiPlusButton: EmojiPlusButton = {
|
|
let result = EmojiPlusButton(dismiss: snDismiss)
|
|
result.set(.width, to: EmojiPlusButton.size)
|
|
result.set(.height, to: EmojiPlusButton.size)
|
|
result.layer.cornerRadius = EmojiPlusButton.size / 2
|
|
result.layer.masksToBounds = true
|
|
return result
|
|
}()
|
|
|
|
private lazy var menuView: UIView = {
|
|
let result = UIView()
|
|
result.layer.shadowColor = UIColor.black.cgColor
|
|
result.layer.shadowOffset = CGSize.zero
|
|
result.layer.shadowOpacity = 0.4
|
|
result.layer.shadowRadius = 4
|
|
return result
|
|
}()
|
|
|
|
private lazy var timestampLabel: UILabel = {
|
|
let result = UILabel()
|
|
let date = viewItem.interaction.dateForUI()
|
|
result.text = DateUtil.formatDate(forDisplay: date)
|
|
result.font = .systemFont(ofSize: Values.verySmallFontSize)
|
|
result.textColor = isLightMode ? .black : .white
|
|
return result
|
|
}()
|
|
|
|
// MARK: Settings
|
|
private static let actionViewHeight: CGFloat = 40
|
|
private static let menuCornerRadius: CGFloat = 8
|
|
|
|
// MARK: Lifecycle
|
|
init(snapshot: UIView, viewItem: ConversationViewItem, frame: CGRect, delegate: ContextMenuActionDelegate, dismiss: @escaping () -> Void) {
|
|
self.snapshot = snapshot
|
|
self.viewItem = viewItem
|
|
self.frame = frame
|
|
self.delegate = delegate
|
|
self.dismiss = dismiss
|
|
super.init(nibName: nil, bundle: nil)
|
|
}
|
|
|
|
override init(nibName: String?, bundle: Bundle?) {
|
|
preconditionFailure("Use init(snapshot:) instead.")
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
preconditionFailure("Use init(coder:) instead.")
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
// Background color
|
|
view.backgroundColor = .clear
|
|
// Blur
|
|
view.addSubview(blurView)
|
|
blurView.pin(to: view)
|
|
// Snapshot
|
|
snapshot.layer.shadowColor = UIColor.black.cgColor
|
|
snapshot.layer.shadowOffset = CGSize.zero
|
|
snapshot.layer.shadowOpacity = 0.4
|
|
snapshot.layer.shadowRadius = 4
|
|
view.addSubview(snapshot)
|
|
snapshot.pin(.left, to: .left, of: view, withInset: frame.origin.x)
|
|
snapshot.pin(.top, to: .top, of: view, withInset: frame.origin.y)
|
|
snapshot.set(.width, to: frame.width)
|
|
snapshot.set(.height, to: frame.height)
|
|
// Timestamp
|
|
view.addSubview(timestampLabel)
|
|
timestampLabel.center(.vertical, in: snapshot)
|
|
let isOutgoing = (viewItem.interaction.interactionType() == .outgoingMessage)
|
|
if isOutgoing {
|
|
timestampLabel.pin(.right, to: .left, of: snapshot, withInset: -Values.smallSpacing)
|
|
} else {
|
|
timestampLabel.pin(.left, to: .right, of: snapshot, withInset: Values.smallSpacing)
|
|
}
|
|
// Emoji reacts
|
|
let emojiBarBackgroundView = UIView()
|
|
emojiBarBackgroundView.backgroundColor = Colors.receivedMessageBackground
|
|
emojiBarBackgroundView.layer.cornerRadius = ContextMenuVC.actionViewHeight / 2
|
|
emojiBarBackgroundView.layer.masksToBounds = true
|
|
emojiBar.addSubview(emojiBarBackgroundView)
|
|
emojiBarBackgroundView.pin(to: emojiBar)
|
|
|
|
emojiBar.addSubview(emojiPlusButton)
|
|
emojiPlusButton.pin(.right, to: .right, of: emojiBar, withInset: -Values.smallSpacing)
|
|
emojiPlusButton.center(.vertical, in: emojiBar)
|
|
|
|
let emojiLabels = ["🙈", "🙉", "🙊", "😈", "🥸", "🐀"].map { EmojiReactsView(for: $0, dismiss: snDismiss) }
|
|
let emojiBarStackView = UIStackView(arrangedSubviews: emojiLabels)
|
|
emojiBarStackView.axis = .horizontal
|
|
emojiBarStackView.spacing = Values.smallSpacing
|
|
emojiBarStackView.layoutMargins = UIEdgeInsets(top: 0, left: Values.smallSpacing, bottom: 0, right: Values.smallSpacing)
|
|
emojiBarStackView.isLayoutMarginsRelativeArrangement = true
|
|
emojiBar.addSubview(emojiBarStackView)
|
|
emojiBarStackView.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top, UIView.VerticalEdge.bottom ], to: emojiBar)
|
|
emojiBarStackView.pin(.right, to: .left, of: emojiPlusButton)
|
|
|
|
view.addSubview(emojiBar)
|
|
// Menu
|
|
let menuBackgroundView = UIView()
|
|
menuBackgroundView.backgroundColor = Colors.receivedMessageBackground
|
|
menuBackgroundView.layer.cornerRadius = ContextMenuVC.menuCornerRadius
|
|
menuBackgroundView.layer.masksToBounds = true
|
|
menuView.addSubview(menuBackgroundView)
|
|
menuBackgroundView.pin(to: menuView)
|
|
let actionViews = ContextMenuVC.actions(for: viewItem, delegate: delegate).map { ActionView(for: $0, dismiss: snDismiss) }
|
|
let menuStackView = UIStackView(arrangedSubviews: actionViews)
|
|
menuStackView.axis = .vertical
|
|
menuView.addSubview(menuStackView)
|
|
menuStackView.pin(to: menuView)
|
|
view.addSubview(menuView)
|
|
// Constrains
|
|
let menuHeight = CGFloat(actionViews.count) * ContextMenuVC.actionViewHeight
|
|
let spacing = Values.smallSpacing
|
|
let margin = max(UIApplication.shared.keyWindow!.safeAreaInsets.bottom, Values.mediumSpacing)
|
|
if frame.maxY + spacing + menuHeight > UIScreen.main.bounds.height - margin {
|
|
menuView.pin(.bottom, to: .top, of: snapshot, withInset: -spacing)
|
|
emojiBar.pin(.top, to: .bottom, of: snapshot, withInset: spacing)
|
|
} else {
|
|
menuView.pin(.top, to: .bottom, of: snapshot, withInset: spacing)
|
|
emojiBar.pin(.bottom, to: .top, of: snapshot, withInset: -spacing)
|
|
}
|
|
switch viewItem.interaction.interactionType() {
|
|
case .outgoingMessage:
|
|
menuView.pin(.right, to: .right, of: snapshot)
|
|
emojiBar.pin(.right, to: .right, of: snapshot)
|
|
case .incomingMessage:
|
|
menuView.pin(.left, to: .left, of: snapshot)
|
|
emojiBar.pin(.left, to: .left, of: snapshot)
|
|
default: break // Should never occur
|
|
}
|
|
// Tap gesture
|
|
let mainTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
|
|
view.addGestureRecognizer(mainTapGestureRecognizer)
|
|
}
|
|
|
|
override func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
UIView.animate(withDuration: 0.25) {
|
|
self.blurView.effect = UIBlurEffect(style: .regular)
|
|
self.menuView.alpha = 1
|
|
}
|
|
}
|
|
|
|
// MARK: Updating
|
|
override func viewDidLayoutSubviews() {
|
|
super.viewDidLayoutSubviews()
|
|
menuView.layer.shadowPath = UIBezierPath(roundedRect: menuView.bounds, cornerRadius: ContextMenuVC.menuCornerRadius).cgPath
|
|
emojiBar.layer.shadowPath = UIBezierPath(roundedRect: emojiBar.bounds, cornerRadius: ContextMenuVC.actionViewHeight / 2).cgPath
|
|
}
|
|
|
|
// MARK: Interaction
|
|
@objc private func handleTap() {
|
|
snDismiss()
|
|
}
|
|
|
|
func snDismiss() {
|
|
UIView.animate(withDuration: 0.25, animations: {
|
|
self.blurView.effect = nil
|
|
self.menuView.alpha = 0
|
|
self.timestampLabel.alpha = 0
|
|
}, completion: { _ in
|
|
self.dismiss()
|
|
self.delegate?.contextMenuDismissed()
|
|
})
|
|
}
|
|
}
|