Skip to content

Commit 376571b

Browse files
[SWU-75] Implemented display of users who reacted to a message
1 parent ec13435 commit 376571b

21 files changed

+614
-81
lines changed

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,15 @@ struct MessageContainerView<Factory: ViewFactory>: View {
7474
)
7575
.overlay(
7676
reactionsShown ?
77-
factory.makeMessageReactionView(message: message)
77+
factory.makeMessageReactionView(
78+
message: message,
79+
onTapGesture: {
80+
handleGestureForMessage(showsMessageActions: false)
81+
},
82+
onLongPressGesture: {
83+
handleGestureForMessage(showsMessageActions: false)
84+
}
85+
)
7886
: nil
7987
)
8088
.background(
@@ -89,20 +97,7 @@ struct MessageContainerView<Factory: ViewFactory>: View {
8997
)
9098
.onTapGesture {}
9199
.onLongPressGesture(perform: {
92-
computeFrame = true
93-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
94-
computeFrame = false
95-
triggerHapticFeedback(style: .medium)
96-
onLongPress(
97-
MessageDisplayInfo(
98-
message: message,
99-
frame: frame,
100-
contentWidth: contentWidth,
101-
isFirst: showsAllInfo
102-
)
103-
)
104-
}
105-
100+
handleGestureForMessage(showsMessageActions: true)
106101
})
107102
.offset(x: self.offsetX)
108103
.simultaneousGesture(
@@ -225,11 +220,29 @@ struct MessageContainerView<Factory: ViewFactory>: View {
225220
self.offsetX = value
226221
}
227222
}
223+
224+
private func handleGestureForMessage(showsMessageActions: Bool) {
225+
computeFrame = true
226+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
227+
computeFrame = false
228+
triggerHapticFeedback(style: .medium)
229+
onLongPress(
230+
MessageDisplayInfo(
231+
message: message,
232+
frame: frame,
233+
contentWidth: contentWidth,
234+
isFirst: showsAllInfo,
235+
showsMessageActions: showsMessageActions
236+
)
237+
)
238+
}
239+
}
228240
}
229241

230242
public struct MessageDisplayInfo {
231243
let message: ChatMessage
232244
let frame: CGRect
233245
let contentWidth: CGFloat
234246
let isFirst: Bool
247+
var showsMessageActions: Bool = true
235248
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
//
2+
// Copyright © 2022 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import StreamChat
6+
import SwiftUI
7+
8+
/// View displaying single user reaction.
9+
struct ReactionUserView: View {
10+
11+
@Injected(\.chatClient) private var chatClient
12+
@Injected(\.fonts) private var fonts
13+
14+
var reaction: ChatMessageReaction
15+
var imageSize: CGFloat
16+
17+
private var isCurrentUser: Bool {
18+
chatClient.currentUserId == reaction.author.id
19+
}
20+
21+
private var authorName: String {
22+
if isCurrentUser {
23+
return L10n.Message.Reactions.currentUser
24+
} else {
25+
return reaction.author.name ?? reaction.author.id
26+
}
27+
}
28+
29+
var body: some View {
30+
VStack {
31+
MessageAvatarView(
32+
author: reaction.author,
33+
size: CGSize(width: imageSize, height: imageSize),
34+
showOnlineIndicator: false
35+
)
36+
.overlay(
37+
VStack {
38+
Spacer()
39+
SingleReactionView(reaction: reaction)
40+
.frame(height: imageSize / 2)
41+
}
42+
)
43+
44+
Text(authorName)
45+
.multilineTextAlignment(.center)
46+
.lineLimit(2)
47+
.font(fonts.footnoteBold)
48+
.frame(width: imageSize)
49+
}
50+
.padding(.vertical)
51+
.padding(.horizontal, 8)
52+
}
53+
}
54+
55+
/// Helper view displaying the reaction overlay.
56+
struct SingleReactionView: View {
57+
58+
@Injected(\.images) private var images
59+
@Injected(\.colors) private var colors
60+
@Injected(\.chatClient) private var chatClient
61+
62+
var reaction: ChatMessageReaction
63+
64+
private var isSentByCurrentUser: Bool {
65+
reaction.author.id == chatClient.currentUserId
66+
}
67+
68+
private var backgroundColor: Color {
69+
isSentByCurrentUser ? Color(colors.background) : Color(colors.background6)
70+
}
71+
72+
var body: some View {
73+
VStack {
74+
Spacer()
75+
HStack {
76+
if !isSentByCurrentUser {
77+
Spacer()
78+
}
79+
80+
if let image = images.availableReactions[reaction.type]?.largeIcon {
81+
VStack(spacing: 0) {
82+
ReactionImageView(
83+
image: image,
84+
isSentByCurrentUser: isSentByCurrentUser,
85+
backgroundColor: backgroundColor
86+
)
87+
88+
ReactionBubbles(
89+
isSentByCurrentUser: isSentByCurrentUser,
90+
backgroundColor: backgroundColor
91+
)
92+
.offset(x: isSentByCurrentUser ? 8 : -8, y: -14)
93+
}
94+
}
95+
96+
if isSentByCurrentUser {
97+
Spacer()
98+
}
99+
}
100+
}
101+
}
102+
}
103+
104+
/// View displaying the reaction image.
105+
struct ReactionImageView: View {
106+
107+
@Injected(\.colors) private var colors
108+
109+
var image: UIImage
110+
var isSentByCurrentUser: Bool
111+
var backgroundColor: Color
112+
113+
private var reactionColor: Color {
114+
isSentByCurrentUser ? colors.tintColor : Color(colors.textLowEmphasis)
115+
}
116+
117+
var body: some View {
118+
Image(uiImage: image)
119+
.resizable()
120+
.foregroundColor(reactionColor)
121+
.frame(width: 16, height: 16)
122+
.padding(.all, 8)
123+
.background(backgroundColor)
124+
.overlay(
125+
Circle()
126+
.strokeBorder(
127+
Color(colors.innerBorder),
128+
lineWidth: 1
129+
)
130+
)
131+
.clipShape(Circle())
132+
}
133+
}
134+
135+
/// View bubbles shown at the bottom of a reaction.
136+
struct ReactionBubbles: View {
137+
138+
@Injected(\.colors) private var colors
139+
140+
var isSentByCurrentUser: Bool
141+
var backgroundColor: Color
142+
143+
var body: some View {
144+
VStack(spacing: 0) {
145+
Spacer()
146+
Circle()
147+
.fill(backgroundColor)
148+
.frame(width: 8, height: 8)
149+
Circle()
150+
.fill(backgroundColor)
151+
.frame(width: 4, height: 4)
152+
}
153+
.rotationEffect(.degrees(isSentByCurrentUser ? -45 : 45))
154+
}
155+
}

0 commit comments

Comments
 (0)