Skip to content

Commit 694c9b9

Browse files
committed
Add delete reaction
1 parent f99dcec commit 694c9b9

File tree

13 files changed

+924
-80
lines changed

13 files changed

+924
-80
lines changed

apple/InlineIOS/Chat/UIMessageView.swift

+39-12
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,23 @@ class UIMessageView: UIView {
123123
private lazy var reactionsFlowView: ReactionsFlowView = {
124124
let view = ReactionsFlowView(outgoing: outgoing)
125125
view.onReactionTap = { [weak self] emoji in
126-
guard let self = self else { return }
127-
Transactions.shared.mutate(transaction: .addReaction(.init(
128-
message: self.message,
129-
emoji: emoji,
130-
userId: Auth.shared.getCurrentUserId() ?? 0,
131-
peerId: self.message.peerId
132-
)))
126+
guard let self else { return }
127+
128+
if let reaction = fullMessage.reactions.filter({ $0.emoji == emoji && $0.userId == Auth.shared.getCurrentUserId() ?? 0 }).first {
129+
Transactions.shared.mutate(transaction: .deleteReaction(.init(
130+
message: message,
131+
emoji: emoji,
132+
peerId: message.peerId,
133+
chatId: message.chatId
134+
)))
135+
} else {
136+
Transactions.shared.mutate(transaction: .addReaction(.init(
137+
message: message,
138+
emoji: emoji,
139+
userId: Auth.shared.getCurrentUserId() ?? 0,
140+
peerId: message.peerId
141+
)))
142+
}
133143
}
134144
return view
135145
}()
@@ -927,7 +937,8 @@ extension UIMessageView: UIContextMenuInteractionDelegate, ContextMenuManagerDel
927937
reactionsStackView.distribution = .fillEqually
928938
reactionsStackView.spacing = 0
929939

930-
reactionsStackView.widthAnchor.constraint(equalToConstant: CGFloat(reactions.count) * reactionButtonSize).isActive = true
940+
reactionsStackView.widthAnchor.constraint(equalToConstant: CGFloat(reactions.count) * reactionButtonSize)
941+
.isActive = true
931942

932943
for (index, reaction) in reactions.enumerated() {
933944
let button = UIButton(type: .system)
@@ -1004,8 +1015,24 @@ extension UIMessageView: UIContextMenuInteractionDelegate, ContextMenuManagerDel
10041015
Self.contextMenuOpen = false
10051016
interaction?.dismissMenu()
10061017

1007-
// Handle the reaction selection
1008-
Transactions.shared.mutate(transaction: .addReaction(.init(message: message, emoji: selectedReaction, userId: Auth.shared.getCurrentUserId() ?? 0, peerId: message.peerId)))
1018+
// check if there was a reaction that we added before delete our reaction otherwise add the reaction
1019+
if let reaction = fullMessage.reactions.filter({ $0.emoji == selectedReaction && $0.userId == Auth.shared.getCurrentUserId() ?? 0 }).first {
1020+
print("deleting reaction", reaction)
1021+
Transactions.shared.mutate(transaction: .deleteReaction(.init(
1022+
message: message,
1023+
emoji: selectedReaction,
1024+
peerId: message.peerId,
1025+
chatId: message.chatId
1026+
)))
1027+
} else {
1028+
print("adding reaction", selectedReaction)
1029+
Transactions.shared.mutate(transaction: .addReaction(.init(
1030+
message: message,
1031+
emoji: selectedReaction,
1032+
userId: Auth.shared.getCurrentUserId() ?? 0,
1033+
peerId: message.peerId
1034+
)))
1035+
}
10091036
}
10101037

10111038
@objc private func handleWillDoTap(_ sender: UIButton) {
@@ -1036,7 +1063,7 @@ extension Character {
10361063
/// A simple emoji is one scalar and presented to the user as an Emoji
10371064
var isSimpleEmoji: Bool {
10381065
guard let firstScalar = unicodeScalars.first else { return false }
1039-
return firstScalar.properties.isEmoji && firstScalar.value > 0x238c
1066+
return firstScalar.properties.isEmoji && firstScalar.value > 0x238C
10401067
}
10411068

10421069
/// Checks if the scalars will be merged into an emoji
@@ -1052,7 +1079,7 @@ extension String {
10521079

10531080
var containsOnlyEmojis: Bool {
10541081
let trimmed = trimmingCharacters(in: .whitespacesAndNewlines)
1055-
return !trimmed.isEmpty && trimmed.allSatisfy { $0.isEmoji }
1082+
return !trimmed.isEmpty && trimmed.allSatisfy(\.isEmoji)
10561083
}
10571084
}
10581085

apple/InlineKit/Sources/InlineKit/RealtimeHelpers/RealtimeUpdates.swift

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Auth
12
import Foundation
23
import GRDB
34
import InlineProtocol
@@ -36,6 +37,9 @@ public actor UpdatesEngine: Sendable, RealtimeUpdatesProtocol {
3637
case let .updateReaction(updateReaction):
3738
try updateReaction.apply(db)
3839

40+
case let .deleteReaction(deleteReaction):
41+
try deleteReaction.apply(db)
42+
3943
default:
4044
break
4145
}
@@ -226,7 +230,7 @@ extension InlineProtocol.UpdateReaction {
226230
_ = try Reaction.save(db, protocolMessage: reaction, publishChanges: true)
227231
let message = try Message.filter(Column("messageId") == reaction.messageID).fetchOne(db)
228232
print("RECIVED UPDATE FOR REACTON ON MESSAGE \(message)")
229-
if let message = message {
233+
if let message {
230234
print("TRIGERRING RELOAD")
231235
db.afterNextTransaction { _ in
232236
Task(priority: .userInitiated) { @MainActor in
@@ -236,3 +240,12 @@ extension InlineProtocol.UpdateReaction {
236240
}
237241
}
238242
}
243+
244+
extension InlineProtocol.UpdateDeleteReaction {
245+
func apply(_ db: Database) throws {
246+
_ = try Reaction.filter(
247+
Column("messageId") == messageID && Column("chatId") == chatID && Column("emoji") == emoji && Column("userId") ==
248+
Auth.shared.getCurrentUserId() ?? 0
249+
).deleteAll(db)
250+
}
251+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import Auth
2+
import Foundation
3+
import GRDB
4+
import InlineProtocol
5+
import Logger
6+
import MultipartFormDataKit
7+
import RealtimeAPI
8+
9+
public struct TransactionDeleteReaction: Transaction {
10+
// Properties
11+
var message: Message
12+
var emoji: String
13+
var peerId: Peer
14+
var chatId: Int64
15+
16+
// Config
17+
public var id = UUID().uuidString
18+
var config = TransactionConfig.default
19+
var date = Date()
20+
21+
public init(message: Message, emoji: String, peerId: Peer, chatId: Int64) {
22+
self.message = message
23+
self.emoji = emoji
24+
self.peerId = peerId
25+
self.chatId = chatId
26+
print(" init peerId", peerId)
27+
}
28+
29+
// Methods
30+
func optimistic() {
31+
Log.shared.debug("Optimistic delete reaction \(message.messageId) \(peerId) \(message.chatId)")
32+
Task(priority: .userInitiated) {
33+
do {
34+
try AppDatabase.shared.dbWriter.write { db in
35+
_ = try Reaction
36+
.filter(
37+
Column("messageId") == message.messageId && Column("chatId") == message
38+
.chatId && Column("emoji") == emoji && Column("userId") == Auth.shared.getCurrentUserId() ?? 0
39+
).deleteAll(db)
40+
print("deleted reaction")
41+
}
42+
} catch {
43+
Log.shared.error("Failed to delete reaction \(error)")
44+
}
45+
46+
Task(priority: .userInitiated) { @MainActor in
47+
MessagesPublisher.shared.messageUpdatedSync(message: message, peer: peerId, animated: true)
48+
}
49+
}
50+
}
51+
52+
func execute() async throws -> [InlineProtocol.Update] {
53+
let result = try await Realtime.shared.invoke(
54+
.deleteReaction,
55+
input: .deleteReaction(DeleteReactionInput.with {
56+
$0.peerID = peerId.toInputPeer()
57+
$0.messageID = message.messageId
58+
$0.emoji = emoji
59+
})
60+
)
61+
62+
guard case let .deleteReaction(response) = result else {
63+
throw DeleteReactionError.failed
64+
}
65+
66+
return response.updates
67+
}
68+
69+
func shouldRetryOnFail(error: Error) -> Bool {
70+
if let error = error as? RealtimeAPIError {
71+
switch error {
72+
case let .rpcError(_, _, code):
73+
switch code {
74+
case 400, 401:
75+
return false
76+
77+
default:
78+
return true
79+
}
80+
default:
81+
return true
82+
}
83+
}
84+
85+
return true
86+
}
87+
88+
func didSucceed(result: [InlineProtocol.Update]) async {
89+
await Realtime.shared.updates.applyBatch(updates: result)
90+
}
91+
92+
func didFail(error: Error?) async {
93+
Log.shared.error("Failed to delete message", error: error)
94+
Task(priority: .userInitiated) {
95+
do {
96+
try AppDatabase.shared.dbWriter.write { db in
97+
_ = try Reaction
98+
.filter(
99+
Column("messageId") == message.messageId && Column("chatId") == message
100+
.chatId && Column("emoji") == emoji && Column("userId") == Auth.shared.getCurrentUserId() ?? 0
101+
).deleteAll(db)
102+
print("deleted reaction")
103+
}
104+
} catch {
105+
Log.shared.error("Failed to delete reaction \(error)")
106+
}
107+
108+
Task(priority: .userInitiated) { @MainActor in
109+
MessagesPublisher.shared.messageUpdatedSync(message: message, peer: peerId, animated: true)
110+
}
111+
}
112+
}
113+
114+
func rollback() async {
115+
let _ = try? await AppDatabase.shared.dbWriter.write { db in
116+
117+
_ = try Reaction
118+
.filter(
119+
Column("messageId") == message.messageId && Column("chatId") == message
120+
.chatId && Column("emoji") == emoji && Column("userId") == Auth.shared.getCurrentUserId() ?? 0
121+
).deleteAll(db)
122+
}
123+
}
124+
125+
enum DeleteReactionError: Error {
126+
case failed
127+
}
128+
}

apple/InlineKit/Sources/InlineKit/Transactions/Transactions.swift

+9
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public enum TransactionType: Codable {
9898
case mockMessage(MockMessageTransaction)
9999
case deleteMessage(TransactionDeleteMessage)
100100
case addReaction(TransactionAddReaction)
101+
case deleteReaction(TransactionDeleteReaction)
101102
var id: String {
102103
transaction.id
103104
// switch self {
@@ -116,6 +117,8 @@ public enum TransactionType: Codable {
116117
t
117118
case let .addReaction(t):
118119
t
120+
case let .deleteReaction(t):
121+
t
119122
}
120123
}
121124

@@ -151,6 +154,9 @@ public enum TransactionType: Codable {
151154
case let .addReaction(transaction):
152155
try container.encode("addReaction", forKey: .type)
153156
try container.encode(transaction, forKey: .transaction)
157+
case let .deleteReaction(transaction):
158+
try container.encode("deleteReaction", forKey: .type)
159+
try container.encode(transaction, forKey: .transaction)
154160
}
155161
}
156162

@@ -171,6 +177,9 @@ public enum TransactionType: Codable {
171177
case "addReaction":
172178
let transaction = try container.decode(TransactionAddReaction.self, forKey: .transaction)
173179
self = .addReaction(transaction)
180+
case "deleteReaction":
181+
let transaction = try container.decode(TransactionDeleteReaction.self, forKey: .transaction)
182+
self = .deleteReaction(transaction)
174183
default:
175184
throw DecodingError.dataCorruptedError(
176185
forKey: .type,

0 commit comments

Comments
 (0)