Merge branch 'dev' into menu-redesign

pull/689/head
ryanzhao 2 years ago
commit 6aa37d4d98

@ -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

@ -134,7 +134,7 @@ public class HomeViewModel {
joinToPagedType: {
let typingIndicator: TypedTableAlias<ThreadTypingIndicator> = TypedTableAlias()
return SQL("LEFT JOIN \(typingIndicator[.threadId]) = \(thread[.id])")
return SQL("LEFT JOIN \(ThreadTypingIndicator.self) ON \(typingIndicator[.threadId]) = \(thread[.id])")
}()
)
],

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -702,6 +702,8 @@
"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.";
/* New conversation screen*/
"vc_new_conversation_title" = "New Conversation";

@ -65,7 +65,7 @@ final class LandingVC: BaseVC {
linkButtonContainer.set(.height, to: Values.onboardingButtonBottomOffset)
linkButtonContainer.addSubview(linkButton)
linkButton.center(.horizontal, in: linkButtonContainer)
let isIPhoneX = (UIApplication.shared.keyWindow!.safeAreaInsets.bottom > 0)
let isIPhoneX = ((UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0) > 0)
linkButton.centerYAnchor.constraint(equalTo: linkButtonContainer.centerYAnchor, constant: isIPhoneX ? -4 : 0).isActive = true
// Button stack view
let buttonStackView = UIStackView(arrangedSubviews: [ registerButton, restoreButton ])

@ -348,10 +348,14 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu
).insert(db)
case .closedGroup:
guard
let closedGroup: ClosedGroup = try? thread.closedGroup.fetchOne(db),
let members: [GroupMember] = try? closedGroup.members.fetchAll(db)
else {
let closedGroupMemberIds: Set<String> = (try? GroupMember
.select(.profileId)
.filter(GroupMember.Columns.groupId == thread.id)
.asRequest(of: String.self)
.fetchSet(db))
.defaulting(to: [])
guard !closedGroupMemberIds.isEmpty else {
SNLog("Inserted an interaction but couldn't find it's associated thread members")
return
}
@ -359,12 +363,12 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu
// Exclude the current user when creating recipient states (as they will never
// receive the message resulting in the message getting flagged as failed)
let userPublicKey: String = getUserHexEncodedPublicKey(db)
try members
.filter { member -> Bool in member.profileId != userPublicKey }
.forEach { member in
try closedGroupMemberIds
.filter { memberId -> Bool in memberId != userPublicKey }
.forEach { memberId in
try RecipientState(
interactionId: interactionId,
recipientId: member.profileId,
recipientId: memberId,
state: .sending
).insert(db)
}

@ -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?
}
}

@ -19,9 +19,9 @@ public protocol OGMCacheType {
var hasPerformedInitialPoll: [String: Bool] { get set }
var timeSinceLastPoll: [String: TimeInterval] { get set }
func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval
var pendingChanges: [OpenGroupAPI.PendingChange] { get set }
func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval
}
// MARK: - OpenGroupManager
@ -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,
@ -1099,10 +1110,10 @@ public final class OpenGroupManager: NSObject {
extension OpenGroupManager {
public class OGMDependencies: SMKDependencies {
internal var _mutableCache: Atomic<OGMCacheType>?
internal var _mutableCache: Atomic<Atomic<OGMCacheType>?>
public var mutableCache: Atomic<OGMCacheType> {
get { Dependencies.getValueSettingIfNull(&_mutableCache) { OpenGroupManager.shared.mutableCache } }
set { _mutableCache = newValue }
set { _mutableCache.mutate { $0 = newValue } }
}
public var cache: OGMCacheType { return mutableCache.wrappedValue }
@ -1123,7 +1134,7 @@ extension OpenGroupManager {
standardUserDefaults: UserDefaultsType? = nil,
date: Date? = nil
) {
_mutableCache = cache
_mutableCache = Atomic(cache)
super.init(
onionApi: onionApi,

@ -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 }

@ -89,7 +89,7 @@ public final class Poller {
private func pollNextSnode(seal: Resolver<Void>) {
let userPublicKey = getUserHexEncodedPublicKey()
let swarm = SnodeAPI.swarmCache[userPublicKey] ?? []
let swarm = SnodeAPI.swarmCache.wrappedValue[userPublicKey] ?? []
let unusedSnodes = swarm.subtracting(usedSnodes)
guard !unusedSnodes.isEmpty else {

@ -6,58 +6,58 @@ import SessionSnodeKit
import SessionUtilitiesKit
public class SMKDependencies: Dependencies {
internal var _onionApi: OnionRequestAPIType.Type?
internal var _onionApi: Atomic<OnionRequestAPIType.Type?>
public var onionApi: OnionRequestAPIType.Type {
get { Dependencies.getValueSettingIfNull(&_onionApi) { OnionRequestAPI.self } }
set { _onionApi = newValue }
set { _onionApi.mutate { $0 = newValue } }
}
internal var _sodium: SodiumType?
internal var _sodium: Atomic<SodiumType?>
public var sodium: SodiumType {
get { Dependencies.getValueSettingIfNull(&_sodium) { Sodium() } }
set { _sodium = newValue }
set { _sodium.mutate { $0 = newValue } }
}
internal var _box: BoxType?
internal var _box: Atomic<BoxType?>
public var box: BoxType {
get { Dependencies.getValueSettingIfNull(&_box) { sodium.getBox() } }
set { _box = newValue }
set { _box.mutate { $0 = newValue } }
}
internal var _genericHash: GenericHashType?
internal var _genericHash: Atomic<GenericHashType?>
public var genericHash: GenericHashType {
get { Dependencies.getValueSettingIfNull(&_genericHash) { sodium.getGenericHash() } }
set { _genericHash = newValue }
set { _genericHash.mutate { $0 = newValue } }
}
internal var _sign: SignType?
internal var _sign: Atomic<SignType?>
public var sign: SignType {
get { Dependencies.getValueSettingIfNull(&_sign) { sodium.getSign() } }
set { _sign = newValue }
set { _sign.mutate { $0 = newValue } }
}
internal var _aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType?
internal var _aeadXChaCha20Poly1305Ietf: Atomic<AeadXChaCha20Poly1305IetfType?>
public var aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType {
get { Dependencies.getValueSettingIfNull(&_aeadXChaCha20Poly1305Ietf) { sodium.getAeadXChaCha20Poly1305Ietf() } }
set { _aeadXChaCha20Poly1305Ietf = newValue }
set { _aeadXChaCha20Poly1305Ietf.mutate { $0 = newValue } }
}
internal var _ed25519: Ed25519Type?
internal var _ed25519: Atomic<Ed25519Type?>
public var ed25519: Ed25519Type {
get { Dependencies.getValueSettingIfNull(&_ed25519) { Ed25519Wrapper() } }
set { _ed25519 = newValue }
set { _ed25519.mutate { $0 = newValue } }
}
internal var _nonceGenerator16: NonceGenerator16ByteType?
internal var _nonceGenerator16: Atomic<NonceGenerator16ByteType?>
public var nonceGenerator16: NonceGenerator16ByteType {
get { Dependencies.getValueSettingIfNull(&_nonceGenerator16) { OpenGroupAPI.NonceGenerator16Byte() } }
set { _nonceGenerator16 = newValue }
set { _nonceGenerator16.mutate { $0 = newValue } }
}
internal var _nonceGenerator24: NonceGenerator24ByteType?
internal var _nonceGenerator24: Atomic<NonceGenerator24ByteType?>
public var nonceGenerator24: NonceGenerator24ByteType {
get { Dependencies.getValueSettingIfNull(&_nonceGenerator24) { OpenGroupAPI.NonceGenerator24Byte() } }
set { _nonceGenerator24 = newValue }
set { _nonceGenerator24.mutate { $0 = newValue } }
}
// MARK: - Initialization
@ -77,15 +77,15 @@ public class SMKDependencies: Dependencies {
standardUserDefaults: UserDefaultsType? = nil,
date: Date? = nil
) {
_onionApi = onionApi
_sodium = sodium
_box = box
_genericHash = genericHash
_sign = sign
_aeadXChaCha20Poly1305Ietf = aeadXChaCha20Poly1305Ietf
_ed25519 = ed25519
_nonceGenerator16 = nonceGenerator16
_nonceGenerator24 = nonceGenerator24
_onionApi = Atomic(onionApi)
_sodium = Atomic(sodium)
_box = Atomic(box)
_genericHash = Atomic(genericHash)
_sign = Atomic(sign)
_aeadXChaCha20Poly1305Ietf = Atomic(aeadXChaCha20Poly1305Ietf)
_ed25519 = Atomic(ed25519)
_nonceGenerator16 = Atomic(nonceGenerator16)
_nonceGenerator24 = Atomic(nonceGenerator24)
super.init(
generalCache: generalCache,

@ -1243,7 +1243,8 @@ class OpenGroupAPISpec: QuickSpec {
whisperMods: false,
whisperTo: nil,
base64EncodedData: nil,
base64EncodedSignature: nil
base64EncodedSignature: nil,
reactions: nil
)
override class var mockResponse: Data? { return try! JSONEncoder().encode(data) }
@ -1612,7 +1613,8 @@ class OpenGroupAPISpec: QuickSpec {
whisperMods: false,
whisperTo: nil,
base64EncodedData: nil,
base64EncodedSignature: nil
base64EncodedSignature: nil,
reactions: nil
)
override class var mockResponse: Data? { return try! JSONEncoder().encode(data) }

@ -204,7 +204,8 @@ class OpenGroupManagerSpec: QuickSpec {
"AAAAAAAAAAAAAAAAAAAAA",
"AA"
].joined(),
base64EncodedSignature: nil
base64EncodedSignature: nil,
reactions: nil
)
testDirectMessage = OpenGroupAPI.DirectMessage(
id: 128,
@ -229,6 +230,7 @@ class OpenGroupManagerSpec: QuickSpec {
try testOpenGroup.insert(db)
try Capability(openGroupServer: testOpenGroup.server, variant: .sogs, isMissing: false).insert(db)
}
mockOGMCache.when { $0.pendingChanges }.thenReturn([])
mockGeneralCache.when { $0.encodedPublicKey }.thenReturn("05\(TestConstants.publicKey)")
mockGenericHash.when { $0.hash(message: anyArray(), outputLength: any()) }.thenReturn([])
mockSodium
@ -2115,7 +2117,8 @@ class OpenGroupManagerSpec: QuickSpec {
whisperMods: false,
whisperTo: nil,
base64EncodedData: nil,
base64EncodedSignature: nil
base64EncodedSignature: nil,
reactions: nil
)
],
for: "testRoom",
@ -2175,7 +2178,8 @@ class OpenGroupManagerSpec: QuickSpec {
whisperMods: false,
whisperTo: nil,
base64EncodedData: Data([1, 2, 3]).base64EncodedString(),
base64EncodedSignature: nil
base64EncodedSignature: nil,
reactions: nil
)
],
for: "testRoom",
@ -2207,7 +2211,8 @@ class OpenGroupManagerSpec: QuickSpec {
whisperMods: false,
whisperTo: nil,
base64EncodedData: Data([1, 2, 3]).base64EncodedString(),
base64EncodedSignature: nil
base64EncodedSignature: nil,
reactions: nil
)
],
for: "testRoom",
@ -2249,7 +2254,8 @@ class OpenGroupManagerSpec: QuickSpec {
whisperMods: false,
whisperTo: nil,
base64EncodedData: Data([1, 2, 3]).base64EncodedString(),
base64EncodedSignature: nil
base64EncodedSignature: nil,
reactions: nil
),
testMessage,
],
@ -2287,7 +2293,8 @@ class OpenGroupManagerSpec: QuickSpec {
whisperMods: false,
whisperTo: nil,
base64EncodedData: nil,
base64EncodedSignature: nil
base64EncodedSignature: nil,
reactions: nil
)
],
for: "testRoom",
@ -2315,7 +2322,8 @@ class OpenGroupManagerSpec: QuickSpec {
whisperMods: false,
whisperTo: nil,
base64EncodedData: nil,
base64EncodedSignature: nil
base64EncodedSignature: nil,
reactions: nil
)
],
for: "testRoom",

@ -23,19 +23,19 @@ extension SMKDependencies {
date: Date? = nil
) -> SMKDependencies {
return SMKDependencies(
onionApi: (onionApi ?? self._onionApi),
generalCache: (generalCache ?? self._generalCache),
storage: (storage ?? self._storage),
sodium: (sodium ?? self._sodium),
box: (box ?? self._box),
genericHash: (genericHash ?? self._genericHash),
sign: (sign ?? self._sign),
aeadXChaCha20Poly1305Ietf: (aeadXChaCha20Poly1305Ietf ?? self._aeadXChaCha20Poly1305Ietf),
ed25519: (ed25519 ?? self._ed25519),
nonceGenerator16: (nonceGenerator16 ?? self._nonceGenerator16),
nonceGenerator24: (nonceGenerator24 ?? self._nonceGenerator24),
standardUserDefaults: (standardUserDefaults ?? self._standardUserDefaults),
date: (date ?? self._date)
onionApi: (onionApi ?? self._onionApi.wrappedValue),
generalCache: (generalCache ?? self._generalCache.wrappedValue),
storage: (storage ?? self._storage.wrappedValue),
sodium: (sodium ?? self._sodium.wrappedValue),
box: (box ?? self._box.wrappedValue),
genericHash: (genericHash ?? self._genericHash.wrappedValue),
sign: (sign ?? self._sign.wrappedValue),
aeadXChaCha20Poly1305Ietf: (aeadXChaCha20Poly1305Ietf ?? self._aeadXChaCha20Poly1305Ietf.wrappedValue),
ed25519: (ed25519 ?? self._ed25519.wrappedValue),
nonceGenerator16: (nonceGenerator16 ?? self._nonceGenerator16.wrappedValue),
nonceGenerator24: (nonceGenerator24 ?? self._nonceGenerator24.wrappedValue),
standardUserDefaults: (standardUserDefaults ?? self._standardUserDefaults.wrappedValue),
date: (date ?? self._date.wrappedValue)
)
}
}

@ -10,4 +10,9 @@ class MockGeneralCache: Mock<GeneralCacheType>, GeneralCacheType {
get { return accept() as? String }
set { accept(args: [newValue]) }
}
var recentReactionTimestamps: [Int64] {
get { return accept() as! [Int64] }
set { accept(args: [newValue]) }
}
}

@ -37,6 +37,11 @@ class MockOGMCache: Mock<OGMCacheType>, OGMCacheType {
set { accept(args: [newValue]) }
}
var pendingChanges: [OpenGroupAPI.PendingChange] {
get { return accept() as! [OpenGroupAPI.PendingChange] }
set { accept(args: [newValue]) }
}
func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval {
return accept(args: [dependencies]) as! TimeInterval
}

@ -24,20 +24,20 @@ extension OpenGroupManager.OGMDependencies {
date: Date? = nil
) -> OpenGroupManager.OGMDependencies {
return OpenGroupManager.OGMDependencies(
cache: (cache ?? self._mutableCache),
onionApi: (onionApi ?? self._onionApi),
generalCache: (generalCache ?? self._generalCache),
storage: (storage ?? self._storage),
sodium: (sodium ?? self._sodium),
box: (box ?? self._box),
genericHash: (genericHash ?? self._genericHash),
sign: (sign ?? self._sign),
aeadXChaCha20Poly1305Ietf: (aeadXChaCha20Poly1305Ietf ?? self._aeadXChaCha20Poly1305Ietf),
ed25519: (ed25519 ?? self._ed25519),
nonceGenerator16: (nonceGenerator16 ?? self._nonceGenerator16),
nonceGenerator24: (nonceGenerator24 ?? self._nonceGenerator24),
standardUserDefaults: (standardUserDefaults ?? self._standardUserDefaults),
date: (date ?? self._date)
cache: (cache ?? self._mutableCache.wrappedValue),
onionApi: (onionApi ?? self._onionApi.wrappedValue),
generalCache: (generalCache ?? self._generalCache.wrappedValue),
storage: (storage ?? self._storage.wrappedValue),
sodium: (sodium ?? self._sodium.wrappedValue),
box: (box ?? self._box.wrappedValue),
genericHash: (genericHash ?? self._genericHash.wrappedValue),
sign: (sign ?? self._sign.wrappedValue),
aeadXChaCha20Poly1305Ietf: (aeadXChaCha20Poly1305Ietf ?? self._aeadXChaCha20Poly1305Ietf.wrappedValue),
ed25519: (ed25519 ?? self._ed25519.wrappedValue),
nonceGenerator16: (nonceGenerator16 ?? self._nonceGenerator16.wrappedValue),
nonceGenerator24: (nonceGenerator24 ?? self._nonceGenerator24.wrappedValue),
standardUserDefaults: (standardUserDefaults ?? self._standardUserDefaults.wrappedValue),
date: (date ?? self._date.wrappedValue)
)
}
}

@ -24,7 +24,7 @@ public final class SnodeAPI {
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
public static var clockOffset: Int64 = 0
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
public static var swarmCache: [String: Set<Snode>] = [:]
public static var swarmCache: Atomic<[String: Set<Snode>]> = Atomic([:])
// MARK: - Namespaces
@ -96,10 +96,11 @@ public final class SnodeAPI {
private static func loadSwarmIfNeeded(for publicKey: String) {
guard !loadedSwarms.contains(publicKey) else { return }
Storage.shared.read { db in
swarmCache[publicKey] = ((try? Snode.fetchSet(db, publicKey: publicKey)) ?? [])
}
let updatedCacheForKey: Set<Snode> = Storage.shared
.read { db in try Snode.fetchSet(db, publicKey: publicKey) }
.defaulting(to: [])
swarmCache.mutate { $0[publicKey] = updatedCacheForKey }
loadedSwarms.insert(publicKey)
}
@ -107,7 +108,8 @@ public final class SnodeAPI {
#if DEBUG
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
#endif
swarmCache[publicKey] = newValue
swarmCache.mutate { $0[publicKey] = newValue }
guard persist else { return }
Storage.shared.write { db in
@ -119,7 +121,7 @@ public final class SnodeAPI {
#if DEBUG
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
#endif
let swarmOrNil = swarmCache[publicKey]
let swarmOrNil = swarmCache.wrappedValue[publicKey]
guard var swarm = swarmOrNil, let index = swarm.firstIndex(of: snode) else { return }
swarm.remove(at: index)
setSwarm(to: swarm, for: publicKey)
@ -460,7 +462,7 @@ public final class SnodeAPI {
public static func getSwarm(for publicKey: String) -> Promise<Set<Snode>> {
loadSwarmIfNeeded(for: publicKey)
if let cachedSwarm = swarmCache[publicKey], cachedSwarm.count >= minSwarmSnodeCount {
if let cachedSwarm = swarmCache.wrappedValue[publicKey], cachedSwarm.count >= minSwarmSnodeCount {
return Promise<Set<Snode>> { $0.fulfill(cachedSwarm) }
}

@ -3,28 +3,28 @@
import Foundation
open class Dependencies {
public var _generalCache: Atomic<GeneralCacheType>?
public var _generalCache: Atomic<Atomic<GeneralCacheType>?>
public var generalCache: Atomic<GeneralCacheType> {
get { Dependencies.getValueSettingIfNull(&_generalCache) { General.cache } }
set { _generalCache = newValue }
set { _generalCache.mutate { $0 = newValue } }
}
public var _storage: Storage?
public var _storage: Atomic<Storage?>
public var storage: Storage {
get { Dependencies.getValueSettingIfNull(&_storage) { Storage.shared } }
set { _storage = newValue }
set { _storage.mutate { $0 = newValue } }
}
public var _standardUserDefaults: UserDefaultsType?
public var _standardUserDefaults: Atomic<UserDefaultsType?>
public var standardUserDefaults: UserDefaultsType {
get { Dependencies.getValueSettingIfNull(&_standardUserDefaults) { UserDefaults.standard } }
set { _standardUserDefaults = newValue }
set { _standardUserDefaults.mutate { $0 = newValue } }
}
public var _date: Date?
public var _date: Atomic<Date?>
public var date: Date {
get { Dependencies.getValueSettingIfNull(&_date) { Date() } }
set { _date = newValue }
set { _date.mutate { $0 = newValue } }
}
// MARK: - Initialization
@ -35,21 +35,29 @@ open class Dependencies {
standardUserDefaults: UserDefaultsType? = nil,
date: Date? = nil
) {
_generalCache = generalCache
_storage = storage
_standardUserDefaults = standardUserDefaults
_date = date
_generalCache = Atomic(generalCache)
_storage = Atomic(storage)
_standardUserDefaults = Atomic(standardUserDefaults)
_date = Atomic(date)
}
// MARK: - Convenience
public static func getValueSettingIfNull<T>(_ maybeValue: inout T?, _ valueGenerator: () -> T) -> T {
guard let value: T = maybeValue else {
public static func getValueSettingIfNull<T>(_ maybeValue: inout Atomic<T?>, _ valueGenerator: () -> T) -> T {
guard let value: T = maybeValue.wrappedValue else {
let value: T = valueGenerator()
maybeValue = value
maybeValue.mutate { $0 = value }
return value
}
return value
}
// 0 libswiftCore.dylib 0x00000001999fd40c _swift_release_dealloc + 32 (HeapObject.cpp:703)
// 1 SessionMessagingKit 0x0000000106aa958c 0x106860000 + 2397580
// 2 libswiftCore.dylib 0x00000001999fd424 _swift_release_dealloc + 56 (HeapObject.cpp:703)
// 3 SessionUtilitiesKit 0x0000000106cbd980 static Dependencies.getValueSettingIfNull<A>(_:_:) + 264 (Dependencies.swift:49)
// 4 SessionMessagingKit 0x0000000106aa90f4 closure #1 in SMKDependencies.sign.getter + 112 (SMKDependencies.swift:17)
// 5 SessionUtilitiesKit 0x0000000106cbd974 static Dependencies.getValueSettingIfNull<A>(_:_:) + 252 (Dependencies.swift:48)
// 6 SessionMessagingKit 0x000000010697aef8 specialized static OpenGroupAPI.sign(_:messageBytes:for:fallbackSigningType:using:) + 1158904 (OpenGroupAPI.swift:1190)
}

Loading…
Cancel
Save