Skip to content

Commit 999cd8d

Browse files
committed
Basic add reaction UI
1 parent d4aa69c commit 999cd8d

File tree

3 files changed

+397
-18
lines changed

3 files changed

+397
-18
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import InlineKit
2+
import UIKit
3+
class MessageReactionView: UIView {
4+
// MARK: - Properties
5+
6+
private let emoji: String
7+
private let count: Int
8+
private let isSelected: Bool
9+
private let outgoing: Bool
10+
11+
var onTap: ((String) -> Void)?
12+
13+
// MARK: - UI Components
14+
15+
private lazy var containerView: UIView = {
16+
let view = UIView()
17+
view.layer.cornerRadius = 12
18+
view.translatesAutoresizingMaskIntoConstraints = false
19+
return view
20+
}()
21+
22+
private lazy var stackView: UIStackView = {
23+
let stack = UIStackView()
24+
stack.axis = .horizontal
25+
stack.spacing = 4
26+
stack.alignment = .center
27+
stack.translatesAutoresizingMaskIntoConstraints = false
28+
return stack
29+
}()
30+
31+
private lazy var emojiLabel: UILabel = {
32+
let label = UILabel()
33+
label.font = UIFont.systemFont(ofSize: 16)
34+
label.text = emoji
35+
return label
36+
}()
37+
38+
private lazy var countLabel: UILabel = {
39+
let label = UILabel()
40+
label.font = UIFont.systemFont(ofSize: 12, weight: .medium)
41+
label.text = "\(count)"
42+
return label
43+
}()
44+
45+
// MARK: - Initialization
46+
47+
init(emoji: String, count: Int, isSelected: Bool, outgoing: Bool) {
48+
self.emoji = emoji
49+
self.count = count
50+
self.isSelected = isSelected
51+
self.outgoing = outgoing
52+
53+
super.init(frame: .zero)
54+
setupView()
55+
}
56+
57+
@available(*, unavailable)
58+
required init?(coder: NSCoder) {
59+
fatalError("init(coder:) has not been implemented")
60+
}
61+
62+
// MARK: - Setup
63+
64+
private func setupView() {
65+
// Configure container appearance
66+
containerView.backgroundColor = isSelected ?
67+
(outgoing ? UIColor.white.withAlphaComponent(0.3) : ColorManager.shared.selectedColor.withAlphaComponent(0.3)) :
68+
(outgoing ? UIColor.white.withAlphaComponent(0.15) : UIColor.systemGray6)
69+
70+
// Configure text colors
71+
countLabel.textColor = outgoing ? .white.withAlphaComponent(0.9) : .darkGray
72+
73+
// Add subviews
74+
addSubview(containerView)
75+
containerView.addSubview(stackView)
76+
77+
stackView.addArrangedSubview(emojiLabel)
78+
stackView.addArrangedSubview(countLabel)
79+
80+
// Setup constraints
81+
NSLayoutConstraint.activate([
82+
containerView.topAnchor.constraint(equalTo: topAnchor),
83+
containerView.leadingAnchor.constraint(equalTo: leadingAnchor),
84+
containerView.trailingAnchor.constraint(equalTo: trailingAnchor),
85+
containerView.bottomAnchor.constraint(equalTo: bottomAnchor),
86+
87+
stackView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 4),
88+
stackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 8),
89+
stackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -8),
90+
stackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -4)
91+
])
92+
93+
// Add tap gesture
94+
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
95+
containerView.addGestureRecognizer(tapGesture)
96+
containerView.isUserInteractionEnabled = true
97+
}
98+
99+
// MARK: - Actions
100+
101+
@objc private func handleTap() {
102+
onTap?(emoji)
103+
}
104+
105+
// MARK: - Layout
106+
107+
override var intrinsicContentSize: CGSize {
108+
let stackSize = stackView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
109+
return CGSize(width: stackSize.width + 16, height: stackSize.height + 8)
110+
}
111+
112+
override func sizeThatFits(_ size: CGSize) -> CGSize {
113+
return intrinsicContentSize
114+
}
115+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import InlineKit
2+
import UIKit
3+
class ReactionsFlowView: UIView {
4+
// MARK: - Properties
5+
6+
var horizontalSpacing: CGFloat = 4
7+
var verticalSpacing: CGFloat = 4
8+
private var outgoing: Bool = false
9+
10+
private var containerStackView: UIStackView!
11+
12+
var onReactionTap: ((String) -> Void)?
13+
14+
// MARK: - Initialization
15+
16+
init(outgoing: Bool) {
17+
self.outgoing = outgoing
18+
super.init(frame: .zero)
19+
setupView()
20+
}
21+
22+
@available(*, unavailable)
23+
required init?(coder: NSCoder) {
24+
fatalError("init(coder:) has not been implemented")
25+
}
26+
27+
private func setupView() {
28+
translatesAutoresizingMaskIntoConstraints = false
29+
backgroundColor = .clear
30+
31+
containerStackView = UIStackView()
32+
containerStackView.axis = .vertical
33+
containerStackView.spacing = verticalSpacing
34+
containerStackView.alignment = .leading
35+
containerStackView.distribution = .fill
36+
containerStackView.translatesAutoresizingMaskIntoConstraints = false
37+
38+
addSubview(containerStackView)
39+
40+
NSLayoutConstraint.activate([
41+
containerStackView.topAnchor.constraint(equalTo: topAnchor),
42+
containerStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
43+
containerStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
44+
containerStackView.bottomAnchor.constraint(equalTo: bottomAnchor)
45+
])
46+
}
47+
48+
// MARK: - Public Methods
49+
50+
func configure(with reactions: [(emoji: String, count: Int, userIds: [Int64])], currentUserId: Int64?) {
51+
// Clear existing content
52+
containerStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
53+
54+
// Create reaction views
55+
let reactionViews = reactions.map { reaction -> MessageReactionView in
56+
let isSelected = currentUserId != nil && reaction.userIds.contains(currentUserId!)
57+
let view = MessageReactionView(
58+
emoji: reaction.emoji,
59+
count: reaction.count,
60+
isSelected: isSelected,
61+
outgoing: outgoing
62+
)
63+
64+
view.onTap = { [weak self] emoji in
65+
self?.onReactionTap?(emoji)
66+
}
67+
68+
return view
69+
}
70+
71+
// Calculate sizes
72+
let sizes = reactionViews.map { $0.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)) }
73+
74+
// Organize into rows
75+
var currentRow = UIStackView()
76+
currentRow.axis = .horizontal
77+
currentRow.spacing = horizontalSpacing
78+
currentRow.alignment = .center
79+
80+
var currentRowWidth: CGFloat = 0
81+
let maxWidth = UIScreen.main.bounds.width * 0.7 // Adjust as needed
82+
83+
for (index, view) in reactionViews.enumerated() {
84+
let viewWidth = sizes[index].width
85+
86+
if currentRowWidth + viewWidth > maxWidth && currentRowWidth > 0 {
87+
// Add the current row and start a new one
88+
containerStackView.addArrangedSubview(currentRow)
89+
90+
currentRow = UIStackView()
91+
currentRow.axis = .horizontal
92+
currentRow.spacing = horizontalSpacing
93+
currentRow.alignment = .center
94+
currentRowWidth = 0
95+
}
96+
97+
currentRow.addArrangedSubview(view)
98+
currentRowWidth += viewWidth + horizontalSpacing
99+
}
100+
101+
// Add the last row if it has any views
102+
if currentRow.arrangedSubviews.count > 0 {
103+
containerStackView.addArrangedSubview(currentRow)
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)