Skip to content
This repository was archived by the owner on Sep 20, 2023. It is now read-only.

Commit e738618

Browse files
authored
Reactions with ContextMenu (#2112)
* reactions with ContextMenu * Remove dead code * add haptics * slightly faster presentation for reactions
1 parent f619a25 commit e738618

File tree

5 files changed

+187
-77
lines changed

5 files changed

+187
-77
lines changed

Diff for: Classes/Issues/Comments/IssueCommentSectionController.swift

+50-11
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import IGListKit
1111
import TUSafariActivity
1212
import Squawk
1313
import GitHubAPI
14+
import ContextMenu
1415

1516
protocol IssueCommentSectionControllerDelegate: class {
1617
func didSelectReply(
@@ -27,7 +28,8 @@ final class IssueCommentSectionController:
2728
IssueCommentReactionCellDelegate,
2829
EditCommentViewControllerDelegate,
2930
MarkdownStyledTextViewDelegate,
30-
IssueCommentDoubleTapDelegate {
31+
IssueCommentDoubleTapDelegate,
32+
ContextMenuDelegate {
3133

3234
private weak var issueCommentDelegate: IssueCommentSectionControllerDelegate?
3335

@@ -37,7 +39,6 @@ final class IssueCommentSectionController:
3739
private let model: IssueDetailsModel
3840
private var hasBeenDeleted = false
3941
private let autocomplete: IssueCommentAutocomplete
40-
private var menuVisible = false
4142

4243
private lazy var webviewCache: WebviewCellHeightCache = {
4344
return WebviewCellHeightCache(sectionController: self)
@@ -183,7 +184,7 @@ final class IssueCommentSectionController:
183184

184185
@discardableResult
185186
private func uncollapse() -> Bool {
186-
guard collapsed, !menuVisible else { return false }
187+
guard collapsed else { return false }
187188
collapsed = false
188189
clearCollapseCells()
189190
collectionContext?.invalidateLayout(for: self, completion: nil)
@@ -468,14 +469,6 @@ final class IssueCommentSectionController:
468469

469470
// MARK: IssueCommentReactionCellDelegate
470471

471-
func willShowMenu(cell: IssueCommentReactionCell) {
472-
menuVisible = true
473-
}
474-
475-
func didHideMenu(cell: IssueCommentReactionCell) {
476-
menuVisible = false
477-
}
478-
479472
func didAdd(cell: IssueCommentReactionCell, reaction: ReactionContent) {
480473
// don't add a reaction if already reacted
481474
guard let reactions = reactionMutation ?? self.object?.reactions,
@@ -518,6 +511,26 @@ final class IssueCommentSectionController:
518511
viewController?.present(alert, animated: trueUnlessReduceMotionEnabled)
519512
}
520513

514+
func didTapAddReaction(cell: IssueCommentReactionCell, sender: UIView) {
515+
guard let viewController = self.viewController else { return }
516+
ContextMenu.shared.show(
517+
sourceViewController: viewController,
518+
viewController: ReactionsMenuViewController(),
519+
options: ContextMenu.Options(
520+
durations: ContextMenu.AnimationDurations(present: 0.2),
521+
containerStyle: ContextMenu.ContainerStyle(
522+
xPadding: -4,
523+
yPadding: 8,
524+
backgroundColor: Styles.Colors.menuBackgroundColor.color
525+
),
526+
menuStyle: .minimal,
527+
hapticsStyle: .medium
528+
),
529+
sourceView: sender,
530+
delegate: self
531+
)
532+
}
533+
521534
// MARK: MarkdownStyledTextViewDelegate
522535

523536
func didTap(cell: MarkdownStyledTextView, attribute: DetectedMarkdownAttribute) {
@@ -535,4 +548,30 @@ final class IssueCommentSectionController:
535548
viewController.dismiss(animated: trueUnlessReduceMotionEnabled)
536549
}
537550

551+
// MARK: ContextMenuDelegate
552+
553+
func contextMenuWillDismiss(viewController: UIViewController, animated: Bool) {}
554+
555+
func contextMenuDidDismiss(viewController: UIViewController, animated: Bool) {
556+
guard let reactionViewController = viewController as? ReactionsMenuViewController,
557+
let reaction = reactionViewController.selectedReaction,
558+
let reactions = reactionMutation ?? self.object?.reactions
559+
else { return }
560+
561+
var index = -1
562+
for (i, model) in viewModels.reversed().enumerated() {
563+
if model is IssueCommentReactionViewModel {
564+
index = viewModels.count - 1 - i
565+
break
566+
}
567+
}
568+
569+
guard index >= 0 else { return }
570+
react(
571+
cell: collectionContext?.cellForItem(at: index, sectionController: self) as? IssueCommentReactionCell,
572+
content: reaction,
573+
isAdd: !reactions.viewerDidReact(reaction: reaction)
574+
)
575+
}
576+
538577
}

Diff for: Classes/Issues/Comments/Reactions/IssueCommentReactionCell.swift

+4-43
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ import SnapKit
1111
import IGListKit
1212

1313
protocol IssueCommentReactionCellDelegate: class {
14-
func willShowMenu(cell: IssueCommentReactionCell)
15-
func didHideMenu(cell: IssueCommentReactionCell)
1614
func didAdd(cell: IssueCommentReactionCell, reaction: ReactionContent)
1715
func didRemove(cell: IssueCommentReactionCell, reaction: ReactionContent)
1816
func didTapMore(cell: IssueCommentReactionCell, sender: UIView)
17+
func didTapAddReaction(cell: IssueCommentReactionCell, sender: UIView)
1918
}
2019

2120
final class IssueCommentReactionCell: IssueCommentBaseCell,
@@ -49,7 +48,7 @@ UICollectionViewDelegateFlowLayout {
4948
addButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10)
5049
addButton.setTitleColor(Styles.Colors.Gray.light.color, for: .normal)
5150
addButton.setImage(UIImage(named: "smiley-small")?.withRenderingMode(.alwaysTemplate), for: .normal)
52-
addButton.addTarget(self, action: #selector(IssueCommentReactionCell.onAddButton), for: .touchUpInside)
51+
addButton.addTarget(self, action: #selector(IssueCommentReactionCell.onAddButton(sender:)), for: .touchUpInside)
5352
addButton.accessibilityLabel = NSLocalizedString("Add reaction", comment: "")
5453
addButton.setContentCompressionResistancePriority(.required, for: .horizontal)
5554
contentView.addSubview(addButton)
@@ -84,20 +83,6 @@ UICollectionViewDelegateFlowLayout {
8483
make.top.bottom.right.equalToSuperview()
8584
make.right.equalTo(moreButton.snp.left).offset(-Styles.Sizes.columnSpacing)
8685
}
87-
88-
let nc = NotificationCenter.default
89-
nc.addObserver(
90-
self,
91-
selector: #selector(onMenuControllerWillShow(notification:)),
92-
name: .UIMenuControllerWillShowMenu,
93-
object: nil
94-
)
95-
nc.addObserver(
96-
self,
97-
selector: #selector(onMenuControllerDidHide(notification:)),
98-
name: .UIMenuControllerDidHideMenu,
99-
object: nil
100-
)
10186
}
10287

10388
required init?(coder aDecoder: NSCoder) {
@@ -123,22 +108,8 @@ UICollectionViewDelegateFlowLayout {
123108
return collectionView.cellForItem(at: path) as? IssueReactionCell
124109
}
125110

126-
@objc private func onAddButton() {
127-
addButton.becomeFirstResponder()
128-
129-
let actions = [
130-
(ReactionContent.thumbsUp.emoji, #selector(IssueCommentReactionCell.onThumbsUp)),
131-
(ReactionContent.thumbsDown.emoji, #selector(IssueCommentReactionCell.onThumbsDown)),
132-
(ReactionContent.laugh.emoji, #selector(IssueCommentReactionCell.onLaugh)),
133-
(ReactionContent.hooray.emoji, #selector(IssueCommentReactionCell.onHooray)),
134-
(ReactionContent.confused.emoji, #selector(IssueCommentReactionCell.onConfused)),
135-
(ReactionContent.heart.emoji, #selector(IssueCommentReactionCell.onHeart))
136-
]
137-
138-
let menu = UIMenuController.shared
139-
menu.menuItems = actions.map { UIMenuItem(title: $0.0, action: $0.1) }
140-
menu.setTargetRect(addButton.imageView?.frame ?? .zero, in: addButton)
141-
menu.setMenuVisible(true, animated: trueUnlessReduceMotionEnabled)
111+
@objc private func onAddButton(sender: UIView) {
112+
delegate?.didTapAddReaction(cell: self, sender: sender)
142113
}
143114

144115
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
@@ -208,16 +179,6 @@ UICollectionViewDelegateFlowLayout {
208179
delegate?.didTapMore(cell: self, sender: sender)
209180
}
210181

211-
// MARK: Notifications
212-
213-
@objc func onMenuControllerWillShow(notification: Notification) {
214-
delegate?.willShowMenu(cell: self)
215-
}
216-
217-
@objc func onMenuControllerDidHide(notification: Notification) {
218-
delegate?.didHideMenu(cell: self)
219-
}
220-
221182
// MARK: ListBindable
222183

223184
func bindViewModel(_ viewModel: Any) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//
2+
// ReactionsMenuViewController.swift
3+
// Freetime
4+
//
5+
// Created by Ryan Nystrom on 8/12/18.
6+
// Copyright © 2018 Ryan Nystrom. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
final class EmojiCell: UICollectionViewCell {
12+
let label = UILabel()
13+
14+
override init(frame: CGRect) {
15+
super.init(frame: frame)
16+
label.textAlignment = .center
17+
label.backgroundColor = .clear
18+
label.font = UIFont.systemFont(ofSize: Styles.Text.body.size + 4)
19+
contentView.addSubview(label)
20+
21+
selectedBackgroundView = UIView()
22+
selectedBackgroundView?.backgroundColor = Styles.Colors.Gray.medium.color
23+
}
24+
25+
required init?(coder aDecoder: NSCoder) {
26+
fatalError("init(coder:) has not been implemented")
27+
}
28+
29+
override func layoutSubviews() {
30+
super.layoutSubviews()
31+
label.frame = contentView.bounds
32+
}
33+
34+
}
35+
36+
final class ReactionsMenuViewController: UICollectionViewController,
37+
UICollectionViewDelegateFlowLayout {
38+
39+
private let reuseIdentifier = "cell"
40+
private let size: CGFloat = 50
41+
private let reactions: [ReactionContent] = [
42+
.thumbsUp,
43+
.hooray,
44+
.thumbsDown,
45+
.heart,
46+
.laugh,
47+
.confused
48+
]
49+
50+
var selectedReaction: ReactionContent? {
51+
guard let item = collectionView?.indexPathsForSelectedItems?.first?.item else { return nil }
52+
return reactions[item]
53+
}
54+
55+
init() {
56+
let layout = UICollectionViewFlowLayout()
57+
layout.minimumLineSpacing = 0
58+
layout.minimumInteritemSpacing = 0
59+
super.init(collectionViewLayout: layout)
60+
}
61+
62+
required init?(coder aDecoder: NSCoder) {
63+
fatalError("init(coder:) has not been implemented")
64+
}
65+
66+
override func viewDidLoad() {
67+
super.viewDidLoad()
68+
collectionView?.backgroundColor = Styles.Colors.menuBackgroundColor.color
69+
collectionView?.register(EmojiCell.self, forCellWithReuseIdentifier: reuseIdentifier)
70+
collectionView?.reloadData()
71+
collectionView?.layoutIfNeeded()
72+
preferredContentSize = CGSize(
73+
width: size * CGFloat(reactions.count),
74+
height: size
75+
)
76+
}
77+
78+
// MARK: UICollectionViewDataSource
79+
80+
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
81+
return reactions.count
82+
}
83+
84+
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
85+
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
86+
if let cell = cell as? EmojiCell {
87+
cell.label.text = reactions[indexPath.item].emoji
88+
}
89+
return cell
90+
}
91+
92+
// MARK: UICollectionViewDelegate
93+
94+
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
95+
dismiss(animated: true)
96+
}
97+
98+
// MARK: UICollectionViewDelegateFlowLayout
99+
100+
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
101+
return CGSize(
102+
width: size,
103+
height: size
104+
)
105+
}
106+
107+
}

0 commit comments

Comments
 (0)