wip: emoji reacts message cell ui
This commit is contained in:
parent
a20afe1c97
commit
692df74af6
|
@ -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 */,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
)
|
||||
"""
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue