Merge pull request #680 from RyanRory/emoji-react-tweak-1

Emoji react tweaks
pull/688/head
RyanZhao 2 years ago committed by GitHub
commit 57eccd1e80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -23,6 +23,7 @@ enum RemoteModel {
let sortOrder: UInt
let category: EmojiCategory
let skinVariations: [String: SkinVariation]?
let shortNames: [String]?
}
struct SkinVariation: Codable {
@ -64,6 +65,7 @@ struct EmojiModel {
let category: RemoteModel.EmojiCategory
let rawName: String
let enumName: String
var shortNames: Set<String>
let variants: [Emoji]
var baseEmoji: Character { variants[0].base }
@ -91,7 +93,10 @@ struct EmojiModel {
category = remoteItem.category
rawName = remoteItem.name
enumName = Self.parseEnumNameFromRemoteItem(remoteItem)
shortNames = Set((remoteItem.shortNames ?? []))
shortNames.insert(rawName.lowercased())
shortNames.insert(enumName.lowercased())
let baseEmojiChar = try Self.codePointsToCharacter(Self.parseCodePointString(remoteItem.unified))
let baseEmoji = Emoji(emojiChar: baseEmojiChar, base: baseEmojiChar, skintoneSequence: .none)
@ -509,7 +514,7 @@ extension EmojiGenerator {
fileHandle.indent {
fileHandle.writeLine("switch self {")
emojiModel.definitions.forEach {
fileHandle.writeLine("case .\($0.enumName): return \"\($0.rawName)\"")
fileHandle.writeLine("case .\($0.enumName): return \"\($0.shortNames.joined(separator:", "))\"")
}
fileHandle.writeLine("}")
}

@ -5792,7 +5792,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 373;
CURRENT_PROJECT_VERSION = 374;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -5865,7 +5865,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 373;
CURRENT_PROJECT_VERSION = 374;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -5931,7 +5931,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 373;
CURRENT_PROJECT_VERSION = 374;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -6005,7 +6005,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 373;
CURRENT_PROJECT_VERSION = 374;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -6943,7 +6943,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 373;
CURRENT_PROJECT_VERSION = 374;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -7015,7 +7015,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 373;
CURRENT_PROJECT_VERSION = 374;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",

@ -1040,6 +1040,15 @@ extension ConversationVC:
return Promise(error: StorageError.objectNotFound)
}
let pendingChange = OpenGroupManager
.addPendingReaction(
emoji: emoji,
id: openGroupServerMessageId,
in: openGroup.roomToken,
on: openGroup.server,
type: .removeAll
)
return OpenGroupAPI
.reactionDeleteAll(
db,
@ -1048,7 +1057,13 @@ extension ConversationVC:
in: openGroup.roomToken,
on: openGroup.server
)
.map { _ in () }
.map { _, response in
OpenGroupManager
.updatePendingChange(
pendingChange,
seqNo: response.seqNo
)
}
}
.done { _ in
Storage.shared.writeAsync { db in
@ -1086,7 +1101,7 @@ extension ConversationVC:
// Perform the sending logic
Storage.shared.writeAsync(
updates: { [weak self] db in
updates: { db in
guard let thread: SessionThread = try SessionThread.fetchOne(db, id: cellViewModel.threadId) else {
return
}
@ -1096,29 +1111,42 @@ extension ConversationVC:
.filter(id: thread.id)
.updateAll(db, SessionThread.Columns.shouldBeVisible.set(to: true))
let pendingReaction: Reaction? = {
if remove {
return try? Reaction
.filter(Reaction.Columns.interactionId == cellViewModel.id)
.filter(Reaction.Columns.authorId == cellViewModel.currentUserPublicKey)
.filter(Reaction.Columns.emoji == emoji)
.fetchOne(db)
} else {
let sortId = Reaction.getSortId(
db,
interactionId: cellViewModel.id,
emoji: emoji
)
return Reaction(
interactionId: cellViewModel.id,
serverHash: nil,
timestampMs: sentTimestamp,
authorId: cellViewModel.currentUserPublicKey,
emoji: emoji,
count: 1,
sortId: sortId
)
}
}()
// Update the database
if remove {
_ = try Reaction
try Reaction
.filter(Reaction.Columns.interactionId == cellViewModel.id)
.filter(Reaction.Columns.authorId == cellViewModel.currentUserPublicKey)
.filter(Reaction.Columns.emoji == emoji)
.deleteAll(db)
}
else {
let sortId = Reaction.getSortId(
db,
interactionId: cellViewModel.id,
emoji: emoji
)
try Reaction(
interactionId: cellViewModel.id,
serverHash: nil,
timestampMs: sentTimestamp,
authorId: cellViewModel.currentUserPublicKey,
emoji: emoji,
count: 1,
sortId: sortId
).insert(db)
try pendingReaction?.insert(db)
// Add it to the recent list
Emoji.addRecent(db, emoji: emoji)
@ -1160,6 +1188,15 @@ extension ConversationVC:
seqNo: response.seqNo
)
}
.catch { [weak self] _ in
OpenGroupManager.removePendingChange(pendingChange)
self?.handleReactionSentFailure(
pendingReaction,
remove: remove
)
}
.retainUntilComplete()
} else {
let pendingChange = OpenGroupManager
@ -1168,7 +1205,7 @@ extension ConversationVC:
id: openGroupServerMessageId,
in: openGroup.roomToken,
on: openGroup.server,
type: .react
type: .add
)
OpenGroupAPI
.reactionAdd(
@ -1185,6 +1222,14 @@ extension ConversationVC:
seqNo: response.seqNo
)
}
.catch { [weak self] _ in
OpenGroupManager.removePendingChange(pendingChange)
self?.handleReactionSentFailure(
pendingReaction,
remove: remove
)
}
.retainUntilComplete()
}
@ -1216,6 +1261,23 @@ extension ConversationVC:
)
}
func handleReactionSentFailure(_ pendingReaction: Reaction?, remove: Bool) {
guard let pendingReaction = pendingReaction else { return }
Storage.shared.writeAsync { db in
// Reverse the database
if remove {
try pendingReaction.insert(db)
}
else {
try Reaction
.filter(Reaction.Columns.interactionId == pendingReaction.interactionId)
.filter(Reaction.Columns.authorId == pendingReaction.authorId)
.filter(Reaction.Columns.emoji == pendingReaction.emoji)
.deleteAll(db)
}
}
}
func showFullEmojiKeyboard(_ cellViewModel: MessageViewModel) {
hideInputAccessoryView()

@ -124,5 +124,15 @@ extension EmojiPickerSheet: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
collectionView.searchText = searchText
}
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
searchBar.showsCancelButton = true
return true
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.showsCancelButton = false
searchBar.resignFirstResponder()
}
}

@ -900,19 +900,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
// MARK: - Convenience
private func getCornersToRound() -> UIRectCorner {
guard viewModel?.isOnlyMessageInCluster == false else { return .allCorners }
let direction: Direction = (viewModel?.variant == .standardOutgoing ? .outgoing : .incoming)
switch (viewModel?.positionInCluster, direction) {
case (.top, .outgoing): return [ .bottomLeft, .topLeft, .topRight ]
case (.middle, .outgoing): return [ .bottomLeft, .topLeft ]
case (.bottom, .outgoing): return [ .bottomRight, .bottomLeft, .topLeft ]
case (.top, .incoming): return [ .topLeft, .topRight, .bottomRight ]
case (.middle, .incoming): return [ .topRight, .bottomRight ]
case (.bottom, .incoming): return [ .topRight, .bottomRight, .bottomLeft ]
case (.none, _): return .allCorners
}
return .allCorners
}
private func getCornerMask(from rectCorner: UIRectCorner) -> CACornerMask {

@ -95,6 +95,7 @@ final class ReactionListSheet: BaseVC {
result.dataSource = self
result.delegate = self
result.register(view: UserCell.self)
result.register(view: FooterCell.self)
result.separatorStyle = .none
result.backgroundColor = .clear
result.showsVerticalScrollIndicator = false
@ -131,6 +132,15 @@ final class ReactionListSheet: BaseVC {
setUpViewHierarchy()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
reactionContainer.scrollToItem(
at: IndexPath(item: lastSelectedReactionIndex, section: 0),
at: .centeredHorizontally,
animated: false
)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
@ -140,7 +150,9 @@ final class ReactionListSheet: BaseVC {
private func setUpViewHierarchy() {
view.addSubview(contentView)
contentView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing, UIView.VerticalEdge.bottom ], to: view)
contentView.set(.height, to: 440)
// Emoji collectionView height + seleted emoji detail height + 5 × user cell height + footer cell height + bottom safe area inset
let contentViewHeight: CGFloat = 100 + 5 * 65 + 45 + UIApplication.shared.keyWindow!.safeAreaInsets.bottom
contentView.set(.height, to: contentViewHeight)
populateContentView()
}
@ -323,7 +335,19 @@ final class ReactionListSheet: BaseVC {
deleteRowsAnimation: .none,
insertRowsAnimation: .none,
reloadRowsAnimation: .none,
interrupt: { $0.changeCount > 100 }
interrupt: { [weak self] changeset in
/// This is the case where there were 6 reactors in total and locally we only have 5 including current user,
/// and current user remove the reaction. There would be 4 reactors locally and we need to show more
/// reactors cell at this moment. After update from sogs, we'll get the all 5 reactors and update the table
/// with 5 reactors and not showing the more reactors cell.
changeset.elementInserted.count == 1 && self?.selectedReactionUserList.count == 4 ||
/// This is the case where there were 5 reactors without current user, and current user reacted. Before we got
/// the update from sogs, we'll have 6 reactors locally and not showing the more reactors cell. After the update,
/// we'll need to update the table and show 5 reactors with the more reactors cell.
changeset.elementDeleted.count == 1 && self?.selectedReactionUserList.count == 6 ||
/// To many changes to make
changeset.changeCount > 100
}
) { [weak self] updatedData in
self?.selectedReactionUserList = updatedData
}
@ -384,10 +408,23 @@ extension ReactionListSheet: UICollectionViewDataSource, UICollectionViewDelegat
extension ReactionListSheet: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.selectedReactionUserList.count
let moreReactorCount = self.reactionSummaries[lastSelectedReactionIndex].number - self.selectedReactionUserList.count
return moreReactorCount > 0 ? self.selectedReactionUserList.count + 1 : self.selectedReactionUserList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard indexPath.row < self.selectedReactionUserList.count else {
let moreReactorCount = self.reactionSummaries[lastSelectedReactionIndex].number - self.selectedReactionUserList.count
let footerCell: FooterCell = tableView.dequeue(type: FooterCell.self, for: indexPath)
footerCell.update(
moreReactorCount: moreReactorCount,
emoji: self.reactionSummaries[lastSelectedReactionIndex].emoji.rawValue
)
footerCell.selectionStyle = .none
return footerCell
}
let cell: UserCell = tableView.dequeue(type: UserCell.self, for: indexPath)
let cellViewModel: MessageViewModel.ReactionInfo = self.selectedReactionUserList[indexPath.row]
cell.update(
@ -407,6 +444,8 @@ extension ReactionListSheet: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard indexPath.row < self.selectedReactionUserList.count else { return }
let cellViewModel: MessageViewModel.ReactionInfo = self.selectedReactionUserList[indexPath.row]
guard
@ -500,6 +539,44 @@ extension ReactionListSheet {
)
}
}
fileprivate final class FooterCell: UITableViewCell {
private lazy var label: UILabel = {
let result = UILabel()
result.textAlignment = .center
result.font = .systemFont(ofSize: Values.smallFontSize)
result.textColor = Colors.grey.withAlphaComponent(0.8)
return result
}()
// MARK: - Initialization
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpViewHierarchy()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setUpViewHierarchy()
}
private func setUpViewHierarchy() {
// Background color
backgroundColor = Colors.cellBackground
contentView.addSubview(label)
label.pin(to: contentView)
label.set(.height, to: 45)
}
func update(moreReactorCount: Int, emoji: String) {
label.text = (moreReactorCount == 1) ?
String(format: "EMOJI_REACTS_MORE_REACTORS_ONE".localized(), "\(emoji)") :
String(format: "EMOJI_REACTS_MORE_REACTORS_MUTIPLE".localized(), "\(moreReactorCount)" ,"\(emoji)")
}
}
}
// MARK: - Delegate

File diff suppressed because it is too large Load Diff

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -702,4 +702,6 @@
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
/* The name for the emoji category 'Travel & Places' */
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@";
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";

@ -369,12 +369,20 @@ public extension Message {
rawReaction.count > 0,
let reactors = rawReaction.reactors
{
// Decide whether we need to ignore all reactions
let pendingChangeRemoveAllReaction: Bool = associatedPendingChanges.contains { pendingChange in
if case .reaction(_, let emoji, let action) = pendingChange.metadata {
return emoji == decodedEmoji && action == .removeAll
}
return false
}
// Decide whether we need to add an extra reaction from current user
let pendingChangeSelfReaction: Bool? = {
// Find the newest 'PendingChange' entry with a matching emoji, if one exists, and
// set the "self reaction" value based on it's action
let maybePendingChange: OpenGroupAPI.PendingChange? = associatedPendingChanges
.sorted(by: { lhs, rhs -> Bool in (lhs.seqNo ?? Int64.max) > (rhs.seqNo ?? Int64.max) })
.sorted(by: { lhs, rhs -> Bool in (lhs.seqNo ?? Int64.max) >= (rhs.seqNo ?? Int64.max) })
.first { pendingChange in
if case .reaction(_, let emoji, _) = pendingChange.metadata {
return emoji == decodedEmoji
@ -390,11 +398,11 @@ public extension Message {
else { return nil }
// Otherwise add/remove accordingly
return (action == .react)
return action == .add
}()
let shouldAddSelfReaction: Bool = (
pendingChangeSelfReaction ??
(rawReaction.you || reactors.contains(userPublicKey))
((rawReaction.you || reactors.contains(userPublicKey)) && !pendingChangeRemoveAllReaction)
)
let count: Int64 = rawReaction.you ? rawReaction.count - 1 : rawReaction.count
@ -408,6 +416,8 @@ public extension Message {
results = results
.appending( // Add the first reaction (with the count)
pendingChangeRemoveAllReaction ?
nil :
desiredReactorIds.first
.map { reactor in
Reaction(
@ -422,7 +432,7 @@ public extension Message {
}
)
.appending( // Add all other reactions
contentsOf: desiredReactorIds.count <= 1 ?
contentsOf: desiredReactorIds.count <= 1 || pendingChangeRemoveAllReaction ?
[] :
desiredReactorIds
.suffix(from: 1)

@ -4,12 +4,18 @@ import Foundation
extension OpenGroupAPI {
public struct PendingChange: Equatable {
enum ChangeType {
public enum ChangeType {
case reaction
}
public enum ReactAction: Equatable {
case add
case remove
case removeAll
}
enum Metadata {
case reaction(messageId: Int64, emoji: String, action: VisibleMessage.VMReaction.Kind)
case reaction(messageId: Int64, emoji: String, action: ReactAction)
}
let server: String

@ -13,7 +13,7 @@ extension OpenGroupAPI {
public let added: Bool
/// The seqNo after the reaction is added.
public let seqNo: Int64
public let seqNo: Int64?
}
public struct ReactionRemoveResponse: Codable, Equatable {
@ -26,7 +26,7 @@ extension OpenGroupAPI {
public let removed: Bool
/// The seqNo after the reaction is removed.
public let seqNo: Int64
public let seqNo: Int64?
}
public struct ReactionRemoveAllResponse: Codable, Equatable {
@ -39,6 +39,6 @@ extension OpenGroupAPI {
public let removed: Int64
/// The seqNo after the reactions is all removed.
public let seqNo: Int64
public let seqNo: Int64?
}
}

@ -764,7 +764,7 @@ public final class OpenGroupManager: NSObject {
id: Int64,
in roomToken: String,
on server: String,
type: VisibleMessage.VMReaction.Kind,
type: OpenGroupAPI.PendingChange.ReactAction,
using dependencies: OGMDependencies = OGMDependencies()
) -> OpenGroupAPI.PendingChange {
let pendingChange = OpenGroupAPI.PendingChange(
@ -787,7 +787,7 @@ public final class OpenGroupManager: NSObject {
public static func updatePendingChange(
_ pendingChange: OpenGroupAPI.PendingChange,
seqNo: Int64,
seqNo: Int64?,
using dependencies: OGMDependencies = OGMDependencies()
) {
dependencies.mutableCache.mutate {
@ -797,6 +797,17 @@ public final class OpenGroupManager: NSObject {
}
}
public static func removePendingChange(
_ pendingChange: OpenGroupAPI.PendingChange,
using dependencies: OGMDependencies = OGMDependencies()
) {
dependencies.mutableCache.mutate {
if let index = $0.pendingChanges.firstIndex(of: pendingChange) {
$0.pendingChanges.remove(at: index)
}
}
}
/// This method specifies if the given capability is supported on a specified Open Group
public static func isOpenGroupSupport(
_ capability: Capability.Variant,

@ -665,6 +665,7 @@ public final class MessageSender {
with error: MessageSenderError,
interactionId: Int64?
) {
// TODO: Revert the local database change
// If the message was a reaction then we don't want to do anything to the original
// interaciton (which the 'interactionId' is pointing to
guard (message as? VisibleMessage)?.reaction == nil else { return }

Loading…
Cancel
Save