wip: emoji reacts message cell ui

This commit is contained in:
ryanzhao 2022-05-18 17:19:11 +10:00
parent a20afe1c97
commit 692df74af6
14 changed files with 311 additions and 4 deletions

View File

@ -140,12 +140,16 @@
7B251C3627D82D9E001A6284 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; };
7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; };
7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; };
7B7037432834B81F000DCF35 /* ReactionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7037422834B81F000DCF35 /* ReactionContainerView.swift */; };
7B7037452834BCC0000DCF35 /* ReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7037442834BCC0000DCF35 /* ReactionView.swift */; };
7B7CB189270430D20079FF93 /* CallMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB188270430D20079FF93 /* CallMessageView.swift */; };
7B7CB18B270591630079FF93 /* ShareLogsModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18A270591630079FF93 /* ShareLogsModal.swift */; };
7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */; };
7B7CB190270FB2150079FF93 /* MiniCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */; };
7B7CB192271508AD0079FF93 /* CallRingTonePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB191271508AD0079FF93 /* CallRingTonePlayer.swift */; };
7B8D5FC428332600008324D9 /* VisibleMessage+Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */; };
7B8D5FC728336093008324D9 /* ReactMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8D5FC628336093008324D9 /* ReactMessage.swift */; };
7B8D5FC9283369CC008324D9 /* ReactMessage+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8D5FC8283369CC008324D9 /* ReactMessage+Conversion.swift */; };
7B93D06A27CF173D00811CB6 /* MessageRequestsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D06927CF173D00811CB6 /* MessageRequestsViewController.swift */; };
7B93D06D27CF175800811CB6 /* MessageRequestsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D06C27CF175800811CB6 /* MessageRequestsCell.swift */; };
7B93D07027CF194000811CB6 /* ConfigurationMessage+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */; };
@ -1131,12 +1135,16 @@
7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = "<group>"; };
7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = "<group>"; };
7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = "<group>"; };
7B7037422834B81F000DCF35 /* ReactionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionContainerView.swift; sourceTree = "<group>"; };
7B7037442834BCC0000DCF35 /* ReactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionView.swift; sourceTree = "<group>"; };
7B7CB188270430D20079FF93 /* CallMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessageView.swift; sourceTree = "<group>"; };
7B7CB18A270591630079FF93 /* ShareLogsModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareLogsModal.swift; sourceTree = "<group>"; };
7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallBanner.swift; sourceTree = "<group>"; };
7B7CB18F270FB2150079FF93 /* MiniCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniCallView.swift; sourceTree = "<group>"; };
7B7CB191271508AD0079FF93 /* CallRingTonePlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallRingTonePlayer.swift; sourceTree = "<group>"; };
7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+Reaction.swift"; sourceTree = "<group>"; };
7B8D5FC628336093008324D9 /* ReactMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactMessage.swift; sourceTree = "<group>"; };
7B8D5FC8283369CC008324D9 /* ReactMessage+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReactMessage+Conversion.swift"; sourceTree = "<group>"; };
7B93D06927CF173D00811CB6 /* MessageRequestsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewController.swift; sourceTree = "<group>"; };
7B93D06C27CF175800811CB6 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = "<group>"; };
7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = "<group>"; };
@ -2083,6 +2091,15 @@
path = "Views & Modals";
sourceTree = "<group>";
};
7B8D5FC528336071008324D9 /* Emoji Reacts */ = {
isa = PBXGroup;
children = (
7B8D5FC628336093008324D9 /* ReactMessage.swift */,
7B8D5FC8283369CC008324D9 /* ReactMessage+Conversion.swift */,
);
path = "Emoji Reacts";
sourceTree = "<group>";
};
7B93D06827CF173D00811CB6 /* Message Requests */ = {
isa = PBXGroup;
children = (
@ -2222,6 +2239,8 @@
B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */,
7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */,
7B7CB188270430D20079FF93 /* CallMessageView.swift */,
7B7037422834B81F000DCF35 /* ReactionContainerView.swift */,
7B7037442834BCC0000DCF35 /* ReactionView.swift */,
);
path = "Content Views";
sourceTree = "<group>";
@ -2559,6 +2578,7 @@
C300A5F02554B08500555489 /* Sending & Receiving */ = {
isa = PBXGroup;
children = (
7B8D5FC528336071008324D9 /* Emoji Reacts */,
C3D9E3B52567685D0040E4F3 /* Attachments */,
B8F5F61925EDE4B0003BF8D4 /* Data Extraction */,
C32C5B01256DC054003C73A2 /* Expiration */,
@ -4715,6 +4735,7 @@
C32C5AAD256DBE8F003C73A2 /* TSInfoMessage.m in Sources */,
C32C5A13256DB7A5003C73A2 /* PushNotificationAPI.swift in Sources */,
7BFD1A8C2747150E00FB91B9 /* TurnServerInfo.swift in Sources */,
7B8D5FC9283369CC008324D9 /* ReactMessage+Conversion.swift in Sources */,
7BD477AA27F15F24004E2822 /* OpenGroupServerIdLookup.swift in Sources */,
C32A026325A801AA000ED5D4 /* NSData+messagePadding.m in Sources */,
C352A3932557883D00338F3E /* JobDelegate.swift in Sources */,
@ -4803,6 +4824,7 @@
C32C5BCC256DC830003C73A2 /* Storage+ClosedGroups.swift in Sources */,
C3A3A0EC256E1949004D228D /* OWSRecipientIdentity.m in Sources */,
B8F5F56525EC8453003BF8D4 /* Notification+Contacts.swift in Sources */,
7B8D5FC728336093008324D9 /* ReactMessage.swift in Sources */,
C32C5AB2256DBE8F003C73A2 /* TSMessage.m in Sources */,
C3A3A0FE256E1A3C004D228D /* TSDatabaseSecondaryIndexes.m in Sources */,
C38D5E8D2575011E00B6A65C /* MessageSender+ClosedGroups.swift in Sources */,
@ -4929,6 +4951,7 @@
B82B408E239DC00D00A248E7 /* DisplayNameVC.swift in Sources */,
B8214A2B25D63EB9009C0F2A /* MessagesTableView.swift in Sources */,
B835246E25C38ABF0089A44F /* ConversationVC.swift in Sources */,
7B7037432834B81F000DCF35 /* ReactionContainerView.swift in Sources */,
B8AF4BB426A5204600583500 /* SendSeedModal.swift in Sources */,
B821494625D4D6FF009C0F2A /* URLModal.swift in Sources */,
B877E24226CA12910007970A /* CallVC.swift in Sources */,
@ -4963,6 +4986,7 @@
34ABC0E421DD20C500ED9469 /* ConversationMessageMapping.swift in Sources */,
B85357C323A1BD1200AAF6CD /* SeedVC.swift in Sources */,
45B5360E206DD8BB00D61655 /* UIResponder+OWS.swift in Sources */,
7B7037452834BCC0000DCF35 /* ReactionView.swift in Sources */,
B8D84ECF25E3108A005A043E /* ExpandingAttachmentsButton.swift in Sources */,
7B7CB190270FB2150079FF93 /* MiniCallView.swift in Sources */,
B875885A264503A6000E60D0 /* JoinOpenGroupModal.swift in Sources */,

View File

@ -821,8 +821,23 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
func react(_ viewItem: ConversationViewItem, with emoji: String) {
print("Ryan Test: \(emoji)")
// TODO: send emoji react message
UserDefaults.standard.addNewRecentlyUsedEmoji(emoji)
guard let message = viewItem.interaction as? TSMessage else { return }
var authorId = getUserHexEncodedPublicKey()
if let incomingMessage = message as? TSIncomingMessage { authorId = incomingMessage.authorId }
let reactMessage = ReactMessage(timestamp: message.timestamp, authorId: authorId, emoji: emoji)
reactMessage.sender = getUserHexEncodedPublicKey()
let thread = self.thread
let sentTimestamp: UInt64 = NSDate.millisecondTimestamp()
let visibleMessage = VisibleMessage()
visibleMessage.sentTimestamp = sentTimestamp
visibleMessage.reaction = .from(reactMessage)
visibleMessage.reaction?.kind = .react
Storage.write { transaction in
message.update(withReaction: reactMessage, transaction: transaction)
// TODO: send emoji react message
// MessageSender.send(<#T##message: Message##Message#>, in: thread, using: <#T##YapDatabaseReadWriteTransaction#>)
}
}
func showFullEmojiKeyboard(_ viewItem: ConversationViewItem) {

View File

@ -0,0 +1,46 @@
final class ReactionContainerView : UIView {
private lazy var containerView: UIStackView = {
let result = UIStackView()
result.axis = .vertical
result.spacing = Values.smallSpacing
return result
}()
private var showingAllReactions = false
// MARK: Lifecycle
init() {
super.init(frame: CGRect.zero)
setUpViewHierarchy()
}
override init(frame: CGRect) {
preconditionFailure("Use init(viewItem:textColor:) instead.")
}
required init?(coder: NSCoder) {
preconditionFailure("Use init(viewItem:textColor:) instead.")
}
private func setUpViewHierarchy() {
addSubview(containerView)
containerView.pin(to: self)
}
public func update(_ reactions: [(String, Int)]) {
for subview in containerView.arrangedSubviews {
containerView.removeArrangedSubview(subview)
}
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.spacing = Values.smallSpacing
for reaction in reactions {
let reactionView = ReactionView(emoji: reaction.0, number: reaction.1)
stackView.addArrangedSubview(reactionView)
}
containerView.addArrangedSubview(stackView)
}
}

View File

@ -0,0 +1,49 @@
import UIKit
final class ReactionView : UIView {
private let emoji: String
private let number: Int
// MARK: Settings
private static let height: CGFloat = 22
// MARK: Lifecycle
init(emoji: String, number: Int) {
self.emoji = emoji
self.number = number
super.init(frame: CGRect.zero)
setUpViewHierarchy()
}
override init(frame: CGRect) {
preconditionFailure("Use init(viewItem:textColor:) instead.")
}
required init?(coder: NSCoder) {
preconditionFailure("Use init(viewItem:textColor:) instead.")
}
private func setUpViewHierarchy() {
let emojiLabel = UILabel()
emojiLabel.text = emoji
emojiLabel.font = .systemFont(ofSize: Values.verySmallFontSize)
let numberLabel = UILabel()
numberLabel.text = self.number < 1000 ? "\(number)" : String(format: "%.2f", Float(number) / 1000) + "k"
numberLabel.font = .systemFont(ofSize: Values.verySmallFontSize)
numberLabel.textColor = Colors.text
let stackView = UIStackView(arrangedSubviews: [ emojiLabel, numberLabel ])
stackView.axis = .horizontal
stackView.spacing = Values.verySmallSpacing
stackView.alignment = .center
stackView.layoutMargins = UIEdgeInsets(top: 0, left: Values.smallSpacing, bottom: 0, right: Values.smallSpacing)
stackView.isLayoutMarginsRelativeArrangement = true
addSubview(stackView)
stackView.pin(to: self)
set(.height, to: ReactionView.height)
backgroundColor = Colors.receivedMessageBackground
layer.cornerRadius = ReactionView.height / 2
}
}

View File

@ -9,11 +9,16 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
private lazy var authorLabelHeightConstraint = authorLabel.set(.height, to: 0)
private lazy var profilePictureViewLeftConstraint = profilePictureView.pin(.left, to: .left, of: self, withInset: VisibleMessageCell.groupThreadHSpacing)
private lazy var profilePictureViewWidthConstraint = profilePictureView.set(.width, to: Values.verySmallProfilePictureSize)
private lazy var bubbleViewLeftConstraint1 = bubbleView.pin(.left, to: .right, of: profilePictureView, withInset: VisibleMessageCell.groupThreadHSpacing)
private lazy var bubbleViewLeftConstraint2 = bubbleView.leftAnchor.constraint(greaterThanOrEqualTo: leftAnchor, constant: VisibleMessageCell.gutterSize)
private lazy var bubbleViewTopConstraint = bubbleView.pin(.top, to: .bottom, of: authorLabel, withInset: VisibleMessageCell.authorLabelBottomSpacing)
private lazy var bubbleViewRightConstraint1 = bubbleView.pin(.right, to: .right, of: self, withInset: -VisibleMessageCell.contactThreadHSpacing)
private lazy var bubbleViewRightConstraint2 = bubbleView.rightAnchor.constraint(lessThanOrEqualTo: rightAnchor, constant: -VisibleMessageCell.gutterSize)
private lazy var reactionContainerViewLeftConstraint = reactionContainerView.pin(.left, to: .left, of: bubbleView)
private lazy var reactionContainerViewRightConstraint = reactionContainerView.pin(.right, to: .right, of: bubbleView)
private lazy var messageStatusImageViewTopConstraint = messageStatusImageView.pin(.top, to: .bottom, of: bubbleView, withInset: 0)
private lazy var messageStatusImageViewWidthConstraint = messageStatusImageView.set(.width, to: VisibleMessageCell.messageStatusImageViewSize)
private lazy var messageStatusImageViewHeightConstraint = messageStatusImageView.set(.height, to: VisibleMessageCell.messageStatusImageViewSize)
@ -81,6 +86,8 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
private lazy var snContentView = UIView()
private lazy var reactionContainerView = ReactionContainerView()
internal lazy var messageStatusImageView: UIImageView = {
let result = UIImageView()
result.contentMode = .scaleAspectFit
@ -163,7 +170,6 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
addSubview(profilePictureView)
profilePictureViewLeftConstraint.isActive = true
profilePictureViewWidthConstraint.isActive = true
profilePictureView.pin(.bottom, to: .bottom, of: self, withInset: -1)
// Moderator icon image view
moderatorIconImageView.set(.width, to: 20)
moderatorIconImageView.set(.height, to: 20)
@ -182,6 +188,10 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
// Content view
bubbleView.addSubview(snContentView)
snContentView.pin(to: bubbleView)
// Reaction view
addSubview(reactionContainerView)
reactionContainerView.pin(.top, to: .bottom, of: bubbleView, withInset: Values.smallSpacing)
reactionContainerViewLeftConstraint.isActive = true
// Message status image view
addSubview(messageStatusImageView)
messageStatusImageViewTopConstraint.isActive = true
@ -244,6 +254,10 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
updateBubbleViewCorners()
// Content view
populateContentView(for: viewItem, message: message)
// Reaction view
reactionContainerViewLeftConstraint.isActive = (direction == .incoming)
reactionContainerViewRightConstraint.isActive = (direction == .outgoing)
populateReaction(for: viewItem, message: message)
// Date break
headerViewTopConstraint.constant = shouldInsetHeader ? Values.mediumSpacing : 1
headerView.subviews.forEach { $0.removeFromSuperview() }
@ -438,6 +452,21 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
}
}
private func populateReaction(for viewItem: ConversationViewItem, message: TSMessage) {
let reactions: OrderedDictionary<String, Int> = OrderedDictionary()
for reaction in message.reactions {
if let reactMessage = reaction as? ReactMessage, let emoji = reactMessage.emoji {
if let number = reactions.value(forKey: emoji) {
reactions.replace(key: emoji, value: number + 1)
} else {
reactions.append(key: emoji, value: 1)
}
}
}
print("Ryan Test: \(reactions.orderedKeys)")
reactionContainerView.update(reactions.orderedItems)
}
override func layoutSubviews() {
super.layoutSubviews()
updateBubbleViewCorners()

View File

@ -20,6 +20,7 @@ typedef NS_ENUM(NSUInteger, TSMessageDirection) {
@class TSAttachment;
@class TSAttachmentStream;
@class TSQuotedMessage;
@class SNReactMessage;
@class YapDatabaseReadWriteTransaction;
extern const NSUInteger kOversizeTextMessageSizeThreshold;
@ -41,6 +42,7 @@ extern const NSUInteger kOversizeTextMessageSizeThreshold;
@property (nonatomic, nullable) NSString *serverHash;
@property (nonatomic) BOOL isDeleted;
@property (nonatomic) BOOL isCallMessage;
@property (nonatomic, readonly) NSMutableArray<SNReactMessage *> *reactions;
- (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread NS_UNAVAILABLE;
@ -88,6 +90,8 @@ extern const NSUInteger kOversizeTextMessageSizeThreshold;
- (void)updateCallMessageWithNewBody:(NSString *)newBody transaction:(YapDatabaseReadWriteTransaction *)transaction;
- (void)updateWithReaction:(SNReactMessage *)reaction transaction:(YapDatabaseReadWriteTransaction *)transaction;
@end
NS_ASSUME_NONNULL_END

View File

@ -29,6 +29,7 @@ const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024;
@property (nonatomic, nullable) NSString *body;
@property (nonatomic) uint32_t expiresInSeconds;
@property (nonatomic) uint64_t expireStartedAt;
@property (nonatomic) NSMutableArray<SNReactMessage *> *reactions;
/**
* The version of the model class's schema last used to serialize this model. Use this to manage data migrations during
@ -88,6 +89,7 @@ const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024;
_serverHash = serverHash;
_isDeleted = false;
_isCallMessage = false;
_reactions = [NSMutableArray new];
return self;
}
@ -137,6 +139,10 @@ const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024;
if (!_attachmentIds) {
_attachmentIds = [NSMutableArray new];
}
if (!_reactions) {
_reactions = [NSMutableArray new];
}
_schemaVersion = OWSMessageSchemaVersion;
@ -451,6 +457,18 @@ const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024;
}];
}
- (void)updateWithReaction:(SNReactMessage *)reaction transaction:(YapDatabaseReadWriteTransaction *)transaction
{
if ([self isKindOfClass:[TSIncomingMessage class]] || [self isKindOfClass:[TSOutgoingMessage class]]) {
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSMessage *message) {
if (![message.reactions containsObject:reaction]) {
[message.reactions addObject:reaction];
}
}];
}
}
@end
NS_ASSUME_NONNULL_END

View File

@ -13,6 +13,7 @@ public final class VisibleMessage : Message {
@objc public var contact: Contact?
@objc public var profile: Profile?
@objc public var openGroupInvitation: OpenGroupInvitation?
@objc public var reaction: Reaction?
public override var isSelfSendValid: Bool { true }
@ -24,6 +25,7 @@ public final class VisibleMessage : Message {
guard super.isValid else { return false }
if !attachmentIDs.isEmpty { return true }
if openGroupInvitation != nil { return true }
if reaction != nil { return true }
if let text = text?.trimmingCharacters(in: .whitespacesAndNewlines), !text.isEmpty { return true }
return false
}
@ -39,6 +41,7 @@ public final class VisibleMessage : Message {
// TODO: Contact
if let profile = coder.decodeObject(forKey: "profile") as! Profile? { self.profile = profile }
if let openGroupInvitation = coder.decodeObject(forKey: "openGroupInvitation") as! OpenGroupInvitation? { self.openGroupInvitation = openGroupInvitation }
if let reaction = coder.decodeObject(forKey: "reaction") as! Reaction? { self.reaction = reaction }
}
public override func encode(with coder: NSCoder) {
@ -51,6 +54,7 @@ public final class VisibleMessage : Message {
// TODO: Contact
coder.encode(profile, forKey: "profile")
coder.encode(openGroupInvitation, forKey: "openGroupInvitation")
coder.encode(reaction, forKey: "reaction")
}
// MARK: Proto Conversion
@ -65,6 +69,8 @@ public final class VisibleMessage : Message {
if let profile = Profile.fromProto(dataMessage) { result.profile = profile }
if let openGroupInvitationProto = dataMessage.openGroupInvitation,
let openGroupInvitation = OpenGroupInvitation.fromProto(openGroupInvitationProto) { result.openGroupInvitation = openGroupInvitation }
if let reactionProto = dataMessage.reaction,
let reaction = Reaction.fromProto(reactionProto) { result.reaction = reaction }
result.syncTarget = dataMessage.syncTarget
return result
}
@ -103,6 +109,10 @@ public final class VisibleMessage : Message {
// TODO: Contact
// Open group invitation
if let openGroupInvitation = openGroupInvitation, let openGroupInvitationProto = openGroupInvitation.toProto() { dataMessage.setOpenGroupInvitation(openGroupInvitationProto) }
// Emoji react
if let reaction = reaction, let reactionProto = reaction.toProto() {
dataMessage.setReaction(reactionProto)
}
// Group context
do {
try setGroupContextIfNeeded(on: dataMessage, using: transaction)
@ -133,8 +143,9 @@ public final class VisibleMessage : Message {
quote: \(quote?.description ?? "null"),
linkPreview: \(linkPreview?.description ?? "null"),
contact: \(contact?.description ?? "null"),
profile: \(profile?.description ?? "null")
"openGroupInvitation": \(openGroupInvitation?.description ?? "null")
profile: \(profile?.description ?? "null"),
reaction: \(reaction?.description ?? "null"),
openGroupInvitation: \(openGroupInvitation?.description ?? "null")
)
"""
}

View File

@ -2434,6 +2434,9 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr
builder.setQuote(_value)
}
builder.setPreview(preview)
if let _value = reaction {
builder.setReaction(_value)
}
if let _value = profile {
builder.setProfile(_value)
}
@ -2503,6 +2506,10 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr
proto.preview = wrappedItems.map { $0.proto }
}
@objc public func setReaction(_ valueParam: SNProtoDataMessageReaction) {
proto.reaction = valueParam.proto
}
@objc public func setProfile(_ valueParam: SNProtoDataMessageLokiProfile) {
proto.profile = valueParam.proto
}
@ -2538,6 +2545,8 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr
@objc public let preview: [SNProtoDataMessagePreview]
@objc public let reaction: SNProtoDataMessageReaction?
@objc public let profile: SNProtoDataMessageLokiProfile?
@objc public let openGroupInvitation: SNProtoDataMessageOpenGroupInvitation?
@ -2600,6 +2609,7 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr
group: SNProtoGroupContext?,
quote: SNProtoDataMessageQuote?,
preview: [SNProtoDataMessagePreview],
reaction: SNProtoDataMessageReaction?,
profile: SNProtoDataMessageLokiProfile?,
openGroupInvitation: SNProtoDataMessageOpenGroupInvitation?,
closedGroupControlMessage: SNProtoDataMessageClosedGroupControlMessage?) {
@ -2608,6 +2618,7 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr
self.group = group
self.quote = quote
self.preview = preview
self.reaction = reaction
self.profile = profile
self.openGroupInvitation = openGroupInvitation
self.closedGroupControlMessage = closedGroupControlMessage
@ -2640,6 +2651,11 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr
var preview: [SNProtoDataMessagePreview] = []
preview = try proto.preview.map { try SNProtoDataMessagePreview.parseProto($0) }
var reaction: SNProtoDataMessageReaction? = nil
if proto.hasReaction {
reaction = try SNProtoDataMessageReaction.parseProto(proto.reaction)
}
var profile: SNProtoDataMessageLokiProfile? = nil
if proto.hasProfile {
profile = try SNProtoDataMessageLokiProfile.parseProto(proto.profile)
@ -2664,6 +2680,7 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr
group: group,
quote: quote,
preview: preview,
reaction: reaction,
profile: profile,
openGroupInvitation: openGroupInvitation,
closedGroupControlMessage: closedGroupControlMessage)

View File

@ -599,6 +599,15 @@ struct SessionProtos_DataMessage {
set {_uniqueStorage()._preview = newValue}
}
var reaction: SessionProtos_DataMessage.Reaction {
get {return _storage._reaction ?? SessionProtos_DataMessage.Reaction()}
set {_uniqueStorage()._reaction = newValue}
}
/// Returns true if `reaction` has been explicitly set.
var hasReaction: Bool {return _storage._reaction != nil}
/// Clears the value of `reaction`. Subsequent reads from it will return its default value.
mutating func clearReaction() {_uniqueStorage()._reaction = nil}
var profile: SessionProtos_DataMessage.LokiProfile {
get {return _storage._profile ?? SessionProtos_DataMessage.LokiProfile()}
set {_uniqueStorage()._profile = newValue}
@ -2178,6 +2187,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
7: .same(proto: "timestamp"),
8: .same(proto: "quote"),
10: .same(proto: "preview"),
11: .same(proto: "reaction"),
101: .same(proto: "profile"),
102: .same(proto: "openGroupInvitation"),
104: .same(proto: "closedGroupControlMessage"),
@ -2194,6 +2204,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
var _timestamp: UInt64? = nil
var _quote: SessionProtos_DataMessage.Quote? = nil
var _preview: [SessionProtos_DataMessage.Preview] = []
var _reaction: SessionProtos_DataMessage.Reaction? = nil
var _profile: SessionProtos_DataMessage.LokiProfile? = nil
var _openGroupInvitation: SessionProtos_DataMessage.OpenGroupInvitation? = nil
var _closedGroupControlMessage: SessionProtos_DataMessage.ClosedGroupControlMessage? = nil
@ -2213,6 +2224,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
_timestamp = source._timestamp
_quote = source._quote
_preview = source._preview
_reaction = source._reaction
_profile = source._profile
_openGroupInvitation = source._openGroupInvitation
_closedGroupControlMessage = source._closedGroupControlMessage
@ -2233,6 +2245,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
if let v = _storage._group, !v.isInitialized {return false}
if let v = _storage._quote, !v.isInitialized {return false}
if !SwiftProtobuf.Internal.areAllInitialized(_storage._preview) {return false}
if let v = _storage._reaction, !v.isInitialized {return false}
if let v = _storage._openGroupInvitation, !v.isInitialized {return false}
if let v = _storage._closedGroupControlMessage, !v.isInitialized {return false}
return true
@ -2256,6 +2269,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
case 7: try { try decoder.decodeSingularUInt64Field(value: &_storage._timestamp) }()
case 8: try { try decoder.decodeSingularMessageField(value: &_storage._quote) }()
case 10: try { try decoder.decodeRepeatedMessageField(value: &_storage._preview) }()
case 11: try { try decoder.decodeSingularMessageField(value: &_storage._reaction) }()
case 101: try { try decoder.decodeSingularMessageField(value: &_storage._profile) }()
case 102: try { try decoder.decodeSingularMessageField(value: &_storage._openGroupInvitation) }()
case 104: try { try decoder.decodeSingularMessageField(value: &_storage._closedGroupControlMessage) }()
@ -2295,6 +2309,9 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
if !_storage._preview.isEmpty {
try visitor.visitRepeatedMessageField(value: _storage._preview, fieldNumber: 10)
}
if let v = _storage._reaction {
try visitor.visitSingularMessageField(value: v, fieldNumber: 11)
}
if let v = _storage._profile {
try visitor.visitSingularMessageField(value: v, fieldNumber: 101)
}
@ -2325,6 +2342,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
if _storage._timestamp != rhs_storage._timestamp {return false}
if _storage._quote != rhs_storage._quote {return false}
if _storage._preview != rhs_storage._preview {return false}
if _storage._reaction != rhs_storage._reaction {return false}
if _storage._profile != rhs_storage._profile {return false}
if _storage._openGroupInvitation != rhs_storage._openGroupInvitation {return false}
if _storage._closedGroupControlMessage != rhs_storage._closedGroupControlMessage {return false}

View File

@ -198,6 +198,7 @@ message DataMessage {
optional uint64 timestamp = 7;
optional Quote quote = 8;
repeated Preview preview = 10;
optional Reaction reaction = 11;
optional LokiProfile profile = 101;
optional OpenGroupInvitation openGroupInvitation = 102;
optional ClosedGroupControlMessage closedGroupControlMessage = 104;

View File

@ -0,0 +1,24 @@
extension ReactMessage {
/// To be used for outgoing messages only.
public static func from(_ reaction: VisibleMessage.Reaction?) -> ReactMessage? {
guard let reaction = reaction else { return nil }
return ReactMessage(
timestamp: reaction.timestamp!,
authorId: reaction.publicKey!,
emoji: reaction.emoji)
}
}
extension VisibleMessage.Reaction {
public static func from(_ reaction: ReactMessage?) -> VisibleMessage.Reaction? {
guard let reaction = reaction else { return nil }
let result = VisibleMessage.Reaction()
result.timestamp = reaction.timestamp
result.publicKey = reaction.authorId
result.emoji = reaction.emoji
return result
}
}

View File

@ -0,0 +1,39 @@
@objc(SNReactMessage)
public final class ReactMessage : MTLModel {
public var timestamp: UInt64?
public var authorId: String?
@objc
public var emoji: String?
@objc
public var sender: String?
@objc
public var messageId: String?
@objc
public init(timestamp: UInt64, authorId: String, emoji: String?) {
self.timestamp = timestamp
self.authorId = authorId
self.emoji = emoji
super.init()
}
@objc
public override init() {
super.init()
}
@objc
public required init!(coder: NSCoder) {
super.init(coder: coder)
}
@objc
public required init(dictionary dictionaryValue: [String: Any]!) throws {
try super.init(dictionary: dictionaryValue)
}
}

View File

@ -102,4 +102,16 @@ public class OrderedDictionary<KeyType: Hashable, ValueType> {
}
return values
}
public var orderedItems: [(KeyType, ValueType)] {
var items = [(KeyType, ValueType)]()
for key in orderedKeys {
guard let value = self.keyValueMap[key] else {
owsFailDebug("Missing value")
continue
}
items.append((key, value))
}
return items
}
}