Merge branch 'dev' into voice-calls
This commit is contained in:
commit
ef41ea22fa
|
@ -198,6 +198,7 @@
|
||||||
B82A0C2F26B7B45200C1BCE3 /* CallUIAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82A0C1B26B7B45200C1BCE3 /* CallUIAdapter.swift */; };
|
B82A0C2F26B7B45200C1BCE3 /* CallUIAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82A0C1B26B7B45200C1BCE3 /* CallUIAdapter.swift */; };
|
||||||
B82A0C3126B7BFF800C1BCE3 /* TSConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82A0C3026B7BFF800C1BCE3 /* TSConstants.swift */; };
|
B82A0C3126B7BFF800C1BCE3 /* TSConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82A0C3026B7BFF800C1BCE3 /* TSConstants.swift */; };
|
||||||
B82A0C3426B7C0C900C1BCE3 /* TSAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82A0C3326B7C0C900C1BCE3 /* TSAccountManager.swift */; };
|
B82A0C3426B7C0C900C1BCE3 /* TSAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82A0C3326B7C0C900C1BCE3 /* TSAccountManager.swift */; };
|
||||||
|
B82A0C3826B9098200C1BCE3 /* MessageInvalidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82A0C3726B9098200C1BCE3 /* MessageInvalidator.swift */; };
|
||||||
B82B40882399EB0E00A248E7 /* LandingVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B40872399EB0E00A248E7 /* LandingVC.swift */; };
|
B82B40882399EB0E00A248E7 /* LandingVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B40872399EB0E00A248E7 /* LandingVC.swift */; };
|
||||||
B82B408A2399EC0600A248E7 /* FakeChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B40892399EC0600A248E7 /* FakeChatView.swift */; };
|
B82B408A2399EC0600A248E7 /* FakeChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B40892399EC0600A248E7 /* FakeChatView.swift */; };
|
||||||
B82B408C239A068800A248E7 /* RegisterVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B408B239A068800A248E7 /* RegisterVC.swift */; };
|
B82B408C239A068800A248E7 /* RegisterVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B408B239A068800A248E7 /* RegisterVC.swift */; };
|
||||||
|
@ -1226,6 +1227,7 @@
|
||||||
B82A0C1B26B7B45200C1BCE3 /* CallUIAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallUIAdapter.swift; sourceTree = "<group>"; };
|
B82A0C1B26B7B45200C1BCE3 /* CallUIAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallUIAdapter.swift; sourceTree = "<group>"; };
|
||||||
B82A0C3026B7BFF800C1BCE3 /* TSConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSConstants.swift; sourceTree = "<group>"; };
|
B82A0C3026B7BFF800C1BCE3 /* TSConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSConstants.swift; sourceTree = "<group>"; };
|
||||||
B82A0C3326B7C0C900C1BCE3 /* TSAccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAccountManager.swift; sourceTree = "<group>"; };
|
B82A0C3326B7C0C900C1BCE3 /* TSAccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAccountManager.swift; sourceTree = "<group>"; };
|
||||||
|
B82A0C3726B9098200C1BCE3 /* MessageInvalidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageInvalidator.swift; sourceTree = "<group>"; };
|
||||||
B82B40872399EB0E00A248E7 /* LandingVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingVC.swift; sourceTree = "<group>"; };
|
B82B40872399EB0E00A248E7 /* LandingVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingVC.swift; sourceTree = "<group>"; };
|
||||||
B82B40892399EC0600A248E7 /* FakeChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeChatView.swift; sourceTree = "<group>"; };
|
B82B40892399EC0600A248E7 /* FakeChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeChatView.swift; sourceTree = "<group>"; };
|
||||||
B82B408B239A068800A248E7 /* RegisterVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterVC.swift; sourceTree = "<group>"; };
|
B82B408B239A068800A248E7 /* RegisterVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterVC.swift; sourceTree = "<group>"; };
|
||||||
|
@ -3327,6 +3329,7 @@
|
||||||
C33FDBC1255A581700E217F9 /* General.swift */,
|
C33FDBC1255A581700E217F9 /* General.swift */,
|
||||||
B8AE760925ABFB00001A84D2 /* GeneralUtilities.h */,
|
B8AE760925ABFB00001A84D2 /* GeneralUtilities.h */,
|
||||||
B8AE760A25ABFB5A001A84D2 /* GeneralUtilities.m */,
|
B8AE760A25ABFB5A001A84D2 /* GeneralUtilities.m */,
|
||||||
|
B82A0C3726B9098200C1BCE3 /* MessageInvalidator.swift */,
|
||||||
C3A71D0A2558989C0043A11F /* MessageWrapper.swift */,
|
C3A71D0A2558989C0043A11F /* MessageWrapper.swift */,
|
||||||
C3A71D4E25589FF30043A11F /* NSData+messagePadding.h */,
|
C3A71D4E25589FF30043A11F /* NSData+messagePadding.h */,
|
||||||
C3A71D4825589FF20043A11F /* NSData+messagePadding.m */,
|
C3A71D4825589FF20043A11F /* NSData+messagePadding.m */,
|
||||||
|
@ -4974,6 +4977,7 @@
|
||||||
C32C5C01256DC9A0003C73A2 /* OWSIdentityManager.m in Sources */,
|
C32C5C01256DC9A0003C73A2 /* OWSIdentityManager.m in Sources */,
|
||||||
C32C59C4256DB41F003C73A2 /* TSContactThread.m in Sources */,
|
C32C59C4256DB41F003C73A2 /* TSContactThread.m in Sources */,
|
||||||
C32C5AB0256DBE8F003C73A2 /* TSOutgoingMessage.m in Sources */,
|
C32C5AB0256DBE8F003C73A2 /* TSOutgoingMessage.m in Sources */,
|
||||||
|
B82A0C3826B9098200C1BCE3 /* MessageInvalidator.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -5325,7 +5329,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 281;
|
CURRENT_PROJECT_VERSION = 287;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
|
@ -5394,7 +5398,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 281;
|
CURRENT_PROJECT_VERSION = 287;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
@ -5455,7 +5459,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 281;
|
CURRENT_PROJECT_VERSION = 287;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
|
@ -5525,7 +5529,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 281;
|
CURRENT_PROJECT_VERSION = 287;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
@ -6410,7 +6414,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 281;
|
CURRENT_PROJECT_VERSION = 287;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -6479,7 +6483,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 281;
|
CURRENT_PROJECT_VERSION = 287;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
|
|
@ -689,7 +689,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
|
||||||
return cancelVoiceMessageRecording()
|
return cancelVoiceMessageRecording()
|
||||||
}
|
}
|
||||||
// Limit voice messages to a minute
|
// Limit voice messages to a minute
|
||||||
audioTimer = Timer.scheduledTimer(withTimeInterval: 60, repeats: false, block: { [weak self] _ in
|
audioTimer = Timer.scheduledTimer(withTimeInterval: 180, repeats: false, block: { [weak self] _ in
|
||||||
self?.snInputView.hideVoiceMessageUI()
|
self?.snInputView.hideVoiceMessageUI()
|
||||||
self?.endVoiceMessageRecording()
|
self?.endVoiceMessageRecording()
|
||||||
})
|
})
|
||||||
|
|
|
@ -54,6 +54,11 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
|
||||||
return messagesTableView.contentSize.height - tableViewUnobscuredHeight
|
return messagesTableView.contentSize.height - tableViewUnobscuredHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isCloseToBottom: Bool {
|
||||||
|
let margin = (self.lastPageTop - self.messagesTableView.contentOffset.y)
|
||||||
|
return margin <= ConversationVC.scrollToBottomMargin
|
||||||
|
}
|
||||||
|
|
||||||
lazy var mnemonic: String = {
|
lazy var mnemonic: String = {
|
||||||
let identityManager = OWSIdentityManager.shared()
|
let identityManager = OWSIdentityManager.shared()
|
||||||
let databaseConnection = identityManager.value(forKey: "dbConnection") as! YapDatabaseConnection
|
let databaseConnection = identityManager.value(forKey: "dbConnection") as! YapDatabaseConnection
|
||||||
|
@ -199,6 +204,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
|
||||||
notificationCenter.addObserver(self, selector: #selector(addOrRemoveBlockedBanner), name: NSNotification.Name(rawValue: kNSNotificationName_BlockListDidChange), object: nil)
|
notificationCenter.addObserver(self, selector: #selector(addOrRemoveBlockedBanner), name: NSNotification.Name(rawValue: kNSNotificationName_BlockListDidChange), object: nil)
|
||||||
notificationCenter.addObserver(self, selector: #selector(handleGroupUpdatedNotification), name: .groupThreadUpdated, object: nil)
|
notificationCenter.addObserver(self, selector: #selector(handleGroupUpdatedNotification), name: .groupThreadUpdated, object: nil)
|
||||||
notificationCenter.addObserver(self, selector: #selector(sendScreenshotNotificationIfNeeded), name: UIApplication.userDidTakeScreenshotNotification, object: nil)
|
notificationCenter.addObserver(self, selector: #selector(sendScreenshotNotificationIfNeeded), name: UIApplication.userDidTakeScreenshotNotification, object: nil)
|
||||||
|
notificationCenter.addObserver(self, selector: #selector(handleMessageSentStatusChanged), name: .messageSentStatusDidChange, object: nil)
|
||||||
// Mentions
|
// Mentions
|
||||||
MentionsManager.populateUserPublicKeyCacheIfNeeded(for: thread.uniqueId!)
|
MentionsManager.populateUserPublicKeyCacheIfNeeded(for: thread.uniqueId!)
|
||||||
// Draft
|
// Draft
|
||||||
|
@ -340,62 +346,34 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
|
||||||
return messagesTableView.reloadData()
|
return messagesTableView.reloadData()
|
||||||
}
|
}
|
||||||
var shouldScrollToBottom = false
|
var shouldScrollToBottom = false
|
||||||
let shouldAnimate = conversationUpdate.shouldAnimateUpdates
|
|
||||||
let batchUpdates: () -> Void = {
|
let batchUpdates: () -> Void = {
|
||||||
for update in conversationUpdate.updateItems! {
|
for update in conversationUpdate.updateItems! {
|
||||||
switch update.updateItemType {
|
switch update.updateItemType {
|
||||||
case .delete:
|
case .delete:
|
||||||
self.messagesTableView.deleteRows(at: [ IndexPath(row: Int(update.oldIndex), section: 0) ], with: .fade)
|
self.messagesTableView.deleteRows(at: [ IndexPath(row: Int(update.oldIndex), section: 0) ], with: .none)
|
||||||
case .insert:
|
case .insert:
|
||||||
// Perform inserts before updates
|
// Perform inserts before updates
|
||||||
self.messagesTableView.insertRows(at: [ IndexPath(row: Int(update.newIndex), section: 0) ], with: .fade)
|
self.messagesTableView.insertRows(at: [ IndexPath(row: Int(update.newIndex), section: 0) ], with: .none)
|
||||||
if update.viewItem?.interaction is TSOutgoingMessage {
|
if update.viewItem?.interaction is TSOutgoingMessage {
|
||||||
shouldScrollToBottom = true
|
shouldScrollToBottom = true
|
||||||
} else {
|
} else {
|
||||||
let margin = (self.lastPageTop - self.messagesTableView.contentOffset.y)
|
shouldScrollToBottom = self.isCloseToBottom
|
||||||
shouldScrollToBottom = margin <= ConversationVC.scrollToBottomMargin
|
|
||||||
}
|
}
|
||||||
case .update:
|
case .update:
|
||||||
self.messagesTableView.reloadRows(at: [ IndexPath(row: Int(update.oldIndex), section: 0) ], with: .fade)
|
self.messagesTableView.reloadRows(at: [ IndexPath(row: Int(update.oldIndex), section: 0) ], with: .none)
|
||||||
let margin = (self.lastPageTop - self.messagesTableView.contentOffset.y)
|
|
||||||
shouldScrollToBottom = margin <= ConversationVC.scrollToBottomMargin
|
|
||||||
default: preconditionFailure()
|
default: preconditionFailure()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let batchUpdatesCompletion: (Bool) -> Void = { isFinished in
|
UIView.performWithoutAnimation {
|
||||||
if shouldScrollToBottom {
|
messagesTableView.performBatchUpdates(batchUpdates) { _ in
|
||||||
self.scrollToBottom(isAnimated: true)
|
|
||||||
} else {
|
|
||||||
// This is a workaround for an issue where after an attachment is sent without the keyboard showing before,
|
|
||||||
// once the keyboard shows, the table view's content offset can be wrong and the last message won't completely show.
|
|
||||||
// This is caused by the main run loop calling some table view update method that sets the content offset back to
|
|
||||||
// the previous value when the keyboard is shown.
|
|
||||||
self.messagesTableView.reloadData()
|
|
||||||
}
|
|
||||||
self.markAllAsRead()
|
|
||||||
}
|
|
||||||
if shouldAnimate {
|
|
||||||
messagesTableView.performBatchUpdates(batchUpdates, completion: batchUpdatesCompletion)
|
|
||||||
} else {
|
|
||||||
// HACK: We use `UIView.animateWithDuration:0` rather than `UIView.performWithAnimation` to work around a
|
|
||||||
// UIKit Crash like:
|
|
||||||
//
|
|
||||||
// *** Assertion failure in -[ConversationViewLayout prepareForCollectionViewUpdates:],
|
|
||||||
// /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3600.7.47/UICollectionViewLayout.m:760
|
|
||||||
// *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'While
|
|
||||||
// preparing update a visible view at <NSIndexPath: 0xc000000011c00016> {length = 2, path = 0 - 142}
|
|
||||||
// wasn't found in the current data model and was not in an update animation. This is an internal
|
|
||||||
// error.'
|
|
||||||
//
|
|
||||||
// I'm unclear if this is a bug in UIKit, or if we're doing something crazy in
|
|
||||||
// ConversationViewLayout#prepareLayout. To reproduce, rapidily insert and delete items into the
|
|
||||||
// conversation.
|
|
||||||
UIView.animate(withDuration: 0) {
|
|
||||||
self.messagesTableView.performBatchUpdates(batchUpdates, completion: batchUpdatesCompletion)
|
|
||||||
if shouldScrollToBottom {
|
if shouldScrollToBottom {
|
||||||
self.scrollToBottom(isAnimated: false)
|
self.scrollToBottom(isAnimated: false)
|
||||||
}
|
}
|
||||||
|
self.markAllAsRead()
|
||||||
|
}
|
||||||
|
if shouldScrollToBottom {
|
||||||
|
self.scrollToBottom(isAnimated: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -430,6 +408,24 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
|
||||||
reloadInputViews()
|
reloadInputViews()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func handleMessageSentStatusChanged() {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
guard let indexPaths = self.messagesTableView.indexPathsForVisibleRows else { return }
|
||||||
|
var indexPathsToReload: [IndexPath] = []
|
||||||
|
for indexPath in indexPaths {
|
||||||
|
guard let cell = self.messagesTableView.cellForRow(at: indexPath) as? VisibleMessageCell else { continue }
|
||||||
|
let isLast = (indexPath.item == (self.messagesTableView.numberOfRows(inSection: 0) - 1))
|
||||||
|
guard !isLast else { continue }
|
||||||
|
if !cell.messageStatusImageView.isHidden {
|
||||||
|
indexPathsToReload.append(indexPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
self.messagesTableView.reloadRows(at: indexPathsToReload, with: .none)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: General
|
// MARK: General
|
||||||
@objc func addOrRemoveBlockedBanner() {
|
@objc func addOrRemoveBlockedBanner() {
|
||||||
func detach() {
|
func detach() {
|
||||||
|
|
|
@ -822,9 +822,13 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
OWSFailDebug(@"Can't find holdover view item.");
|
OWSFailDebug(@"Can't find holdover view item.");
|
||||||
return [self.delegate conversationViewModelDidUpdate:ConversationUpdate.reloadUpdate];
|
return [self.delegate conversationViewModelDidUpdate:ConversationUpdate.reloadUpdate];
|
||||||
}
|
}
|
||||||
if (!viewItem.hasCachedLayoutState) {
|
|
||||||
[updatedItemSet addObject:itemId];
|
if ([viewItem.interaction isKindOfClass:TSMessage.class]) {
|
||||||
[updatedNeighborItemSet addObject:itemId];
|
TSMessage *message = (TSMessage *)viewItem.interaction;
|
||||||
|
if ([MessageInvalidator isInvalidated:message]) {
|
||||||
|
[updatedItemSet addObject:itemId];
|
||||||
|
[updatedNeighborItemSet addObject:itemId];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -864,6 +868,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
oldViewItemCount:oldItemIdList.count
|
oldViewItemCount:oldItemIdList.count
|
||||||
updatedNeighborItemSet:updatedNeighborItemSet];
|
updatedNeighborItemSet:updatedNeighborItemSet];
|
||||||
|
|
||||||
|
for (NSString *itemID in updatedItemSet) {
|
||||||
|
[MessageInvalidator markAsUpdated:itemID];
|
||||||
|
}
|
||||||
|
|
||||||
return [self.delegate
|
return [self.delegate
|
||||||
conversationViewModelDidUpdate:[ConversationUpdate diffUpdateWithUpdateItems:updateItems
|
conversationViewModelDidUpdate:[ConversationUpdate diffUpdateWithUpdateItems:updateItems
|
||||||
shouldAnimateUpdates:shouldAnimateUpdates]];
|
shouldAnimateUpdates:shouldAnimateUpdates]];
|
||||||
|
|
|
@ -81,7 +81,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
|
||||||
|
|
||||||
private lazy var snContentView = UIView()
|
private lazy var snContentView = UIView()
|
||||||
|
|
||||||
private lazy var messageStatusImageView: UIImageView = {
|
internal lazy var messageStatusImageView: UIImageView = {
|
||||||
let result = UIImageView()
|
let result = UIImageView()
|
||||||
result.contentMode = .scaleAspectFit
|
result.contentMode = .scaleAspectFit
|
||||||
result.layer.cornerRadius = VisibleMessageCell.messageStatusImageViewSize / 2
|
result.layer.cornerRadius = VisibleMessageCell.messageStatusImageViewSize / 2
|
||||||
|
|
|
@ -515,7 +515,7 @@ CGFloat kIconViewLength = 24;
|
||||||
[weakSelf.navigationController pushViewController:vc animated:YES];
|
[weakSelf.navigationController pushViewController:vc animated:YES];
|
||||||
}]];
|
}]];
|
||||||
|
|
||||||
if (self.isOpenGroup) {
|
if (self.isGroupThread) {
|
||||||
// Notification Settings
|
// Notification Settings
|
||||||
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
|
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
|
||||||
UITableViewCell *cell = [OWSTableItem newCell];
|
UITableViewCell *cell = [OWSTableItem newCell];
|
||||||
|
@ -861,17 +861,6 @@ CGFloat kIconViewLength = 24;
|
||||||
[self.thread updateWithMutedUntilDate:nil transaction:transaction];
|
[self.thread updateWithMutedUntilDate:nil transaction:transaction];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
if (self.isClosedGroup) {
|
|
||||||
NSString *groupPublicKey = [LKGroupUtilities getDecodedGroupID:((TSGroupThread *)self.thread).groupModel.groupId];
|
|
||||||
NSString *userPublicKey = [SNGeneralUtilities getUserPublicKey];
|
|
||||||
if (uiSwitch.isOn) {
|
|
||||||
[[LKPushNotificationAPI performOperation:ClosedGroupOperationUnsubscribe
|
|
||||||
forClosedGroupWithPublicKey:groupPublicKey userPublicKey:userPublicKey] retainUntilComplete];
|
|
||||||
} else {
|
|
||||||
[[LKPushNotificationAPI performOperation:ClosedGroupOperationSubscribe
|
|
||||||
forClosedGroupWithPublicKey:groupPublicKey userPublicKey:userPublicKey] retainUntilComplete];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)blockConversationSwitchDidChange:(id)sender
|
- (void)blockConversationSwitchDidChange:(id)sender
|
||||||
|
@ -1002,7 +991,7 @@ CGFloat kIconViewLength = 24;
|
||||||
UISwitch *uiSwitch = (UISwitch *)sender;
|
UISwitch *uiSwitch = (UISwitch *)sender;
|
||||||
BOOL isEnabled = uiSwitch.isOn;
|
BOOL isEnabled = uiSwitch.isOn;
|
||||||
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||||
[(TSGroupThread *)self.thread setisOnlyNotifyingForMentions:isEnabled withTransaction:transaction];
|
[(TSGroupThread *)self.thread setIsOnlyNotifyingForMentions:isEnabled withTransaction:transaction];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ final class DownloadAttachmentModal : Modal {
|
||||||
contact.isTrusted = true
|
contact.isTrusted = true
|
||||||
Storage.write(with: { transaction in
|
Storage.write(with: { transaction in
|
||||||
Storage.shared.setContact(contact, using: transaction)
|
Storage.shared.setContact(contact, using: transaction)
|
||||||
message.touch(with: transaction)
|
MessageInvalidator.invalidate(message, with: transaction)
|
||||||
}, completion: {
|
}, completion: {
|
||||||
Storage.shared.resumeAttachmentDownloadJobsIfNeeded(for: message.uniqueThreadId)
|
Storage.shared.resumeAttachmentDownloadJobsIfNeeded(for: message.uniqueThreadId)
|
||||||
})
|
})
|
||||||
|
|
|
@ -157,8 +157,7 @@ final class ConversationCell : UITableViewCell {
|
||||||
// MARK: Updating
|
// MARK: Updating
|
||||||
private func update() {
|
private func update() {
|
||||||
AssertIsOnMainThread()
|
AssertIsOnMainThread()
|
||||||
guard let thread = threadViewModel?.threadRecord, let threadID = thread.uniqueId else { return }
|
guard let thread = threadViewModel?.threadRecord else { return }
|
||||||
MentionsManager.populateUserPublicKeyCacheIfNeeded(for: threadID) // FIXME: This is a terrible place to do this
|
|
||||||
let isBlocked: Bool
|
let isBlocked: Bool
|
||||||
if let thread = thread as? TSContactThread {
|
if let thread = thread as? TSContactThread {
|
||||||
isBlocked = SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(thread.contactSessionID())
|
isBlocked = SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(thread.contactSessionID())
|
||||||
|
|
|
@ -14,7 +14,7 @@ public final class MentionUtilities : NSObject {
|
||||||
MentionsManager.populateUserPublicKeyCacheIfNeeded(for: threadID, in: transaction)
|
MentionsManager.populateUserPublicKeyCacheIfNeeded(for: threadID, in: transaction)
|
||||||
}
|
}
|
||||||
var string = string
|
var string = string
|
||||||
let regex = try! NSRegularExpression(pattern: "@[0-9a-fA-F]*", options: [])
|
let regex = try! NSRegularExpression(pattern: "@[0-9a-fA-F]{66}", options: [])
|
||||||
let knownPublicKeys = MentionsManager.userPublicKeyCache[threadID] ?? [] // Should always be populated at this point
|
let knownPublicKeys = MentionsManager.userPublicKeyCache[threadID] ?? [] // Should always be populated at this point
|
||||||
var mentions: [(range: NSRange, publicKey: String)] = []
|
var mentions: [(range: NSRange, publicKey: String)] = []
|
||||||
var outerMatch = regex.firstMatch(in: string, options: .withoutAnchoringBounds, range: NSRange(location: 0, length: string.utf16.count))
|
var outerMatch = regex.firstMatch(in: string, options: .withoutAnchoringBounds, range: NSRange(location: 0, length: string.utf16.count))
|
||||||
|
@ -44,9 +44,4 @@ public final class MentionUtilities : NSObject {
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func isUserMentioned(in string: String) -> Bool {
|
|
||||||
let userPublicKey = getUserHexEncodedPublicKey()
|
|
||||||
return string.contains("@\(userPublicKey)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ extension Storage {
|
||||||
pointer.state = state
|
pointer.state = state
|
||||||
pointer.save(with: transaction)
|
pointer.save(with: transaction)
|
||||||
guard let tsMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) else { return }
|
guard let tsMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) else { return }
|
||||||
tsMessage.touch(with: transaction)
|
MessageInvalidator.invalidate(tsMessage, with: transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Also touches the associated message.
|
/// Also touches the associated message.
|
||||||
|
@ -75,7 +75,7 @@ extension Storage {
|
||||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||||
stream.save(with: transaction)
|
stream.save(with: transaction)
|
||||||
guard let tsMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) else { return }
|
guard let tsMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) else { return }
|
||||||
tsMessage.touch(with: transaction)
|
MessageInvalidator.invalidate(tsMessage, with: transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static let receivedMessageTimestampsCollection = "ReceivedMessageTimestampsCollection"
|
private static let receivedMessageTimestampsCollection = "ReceivedMessageTimestampsCollection"
|
||||||
|
|
|
@ -118,12 +118,14 @@ public final class AttachmentUploadJob : NSObject, Job, NSCoding { // NSObject/N
|
||||||
delegate?.handleJobSucceeded(self)
|
delegate?.handleJobSucceeded(self)
|
||||||
SNMessagingKitConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID)
|
SNMessagingKitConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID)
|
||||||
Storage.shared.write(with: { transaction in
|
Storage.shared.write(with: { transaction in
|
||||||
var interaction: TSInteraction?
|
var message: TSMessage?
|
||||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||||
TSDatabaseSecondaryIndexes.enumerateMessages(withTimestamp: self.message.sentTimestamp!, with: { _, key, _ in
|
TSDatabaseSecondaryIndexes.enumerateMessages(withTimestamp: self.message.sentTimestamp!, with: { _, key, _ in
|
||||||
interaction = TSInteraction.fetch(uniqueId: key, transaction: transaction)
|
message = TSMessage.fetch(uniqueId: key, transaction: transaction)
|
||||||
}, using: transaction)
|
}, using: transaction)
|
||||||
interaction?.touch(with: transaction) // To refresh the associated message cell and hide the loader
|
if let message = message {
|
||||||
|
MessageInvalidator.invalidate(message, with: transaction)
|
||||||
|
}
|
||||||
}, completion: { })
|
}, completion: { })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -288,7 +288,11 @@ extension MessageReceiver {
|
||||||
// Notify the user if needed
|
// Notify the user if needed
|
||||||
guard (isMainAppAndActive || isBackgroundPoll), let tsIncomingMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) as? TSIncomingMessage,
|
guard (isMainAppAndActive || isBackgroundPoll), let tsIncomingMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) as? TSIncomingMessage,
|
||||||
let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return tsMessageID }
|
let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return tsMessageID }
|
||||||
SSKEnvironment.shared.notificationsManager!.notifyUser(for: tsIncomingMessage, in: thread, transaction: transaction)
|
DispatchQueue.main.async {
|
||||||
|
Storage.read { transaction in
|
||||||
|
SSKEnvironment.shared.notificationsManager!.notifyUser(for: tsIncomingMessage, in: thread, transaction: transaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
return tsMessageID
|
return tsMessageID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -330,7 +330,6 @@ public final class MessageSender : NSObject {
|
||||||
if let tsMessage = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) {
|
if let tsMessage = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) {
|
||||||
// Track the open group server message ID
|
// Track the open group server message ID
|
||||||
tsMessage.openGroupServerMessageID = message.openGroupServerMessageID ?? 0
|
tsMessage.openGroupServerMessageID = message.openGroupServerMessageID ?? 0
|
||||||
tsMessage.save(with: transaction)
|
|
||||||
// Mark the message as sent
|
// Mark the message as sent
|
||||||
var recipients = [ message.recipient! ]
|
var recipients = [ message.recipient! ]
|
||||||
if case .closedGroup(_) = destination, let threadID = message.threadID, // threadID should always be set at this point
|
if case .closedGroup(_) = destination, let threadID = message.threadID, // threadID should always be set at this point
|
||||||
|
@ -340,6 +339,8 @@ public final class MessageSender : NSObject {
|
||||||
recipients.forEach { recipient in
|
recipients.forEach { recipient in
|
||||||
tsMessage.update(withSentRecipient: recipient, wasSentByUD: true, transaction: transaction)
|
tsMessage.update(withSentRecipient: recipient, wasSentByUD: true, transaction: transaction)
|
||||||
}
|
}
|
||||||
|
tsMessage.save(with: transaction)
|
||||||
|
NotificationCenter.default.post(name: .messageSentStatusDidChange, object: nil, userInfo: nil)
|
||||||
// Start the disappearing messages timer if needed
|
// Start the disappearing messages timer if needed
|
||||||
OWSDisappearingMessagesJob.shared().startAnyExpiration(for: tsMessage, expirationStartedAt: NSDate.millisecondTimestamp(), transaction: transaction)
|
OWSDisappearingMessagesJob.shared().startAnyExpiration(for: tsMessage, expirationStartedAt: NSDate.millisecondTimestamp(), transaction: transaction)
|
||||||
}
|
}
|
||||||
|
@ -358,6 +359,8 @@ public final class MessageSender : NSObject {
|
||||||
|
|
||||||
public static func handleFailedMessageSend(_ message: Message, with error: Swift.Error, using transaction: Any) {
|
public static func handleFailedMessageSend(_ message: Message, with error: Swift.Error, using transaction: Any) {
|
||||||
guard let tsMessage = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) else { return }
|
guard let tsMessage = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) else { return }
|
||||||
tsMessage.update(sendingError: error, transaction: transaction as! YapDatabaseReadWriteTransaction)
|
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||||
|
tsMessage.update(sendingError: error, transaction: transaction)
|
||||||
|
MessageInvalidator.invalidate(tsMessage, with: transaction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,13 +88,7 @@ public final class PushNotificationAPI : NSObject {
|
||||||
}
|
}
|
||||||
// Subscribe to all closed groups
|
// Subscribe to all closed groups
|
||||||
Storage.shared.getUserClosedGroupPublicKeys().forEach { closedGroupPublicKey in
|
Storage.shared.getUserClosedGroupPublicKeys().forEach { closedGroupPublicKey in
|
||||||
let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(closedGroupPublicKey)
|
performOperation(.subscribe, for: closedGroupPublicKey, publicKey: publicKey)
|
||||||
let threadOrNil = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID))
|
|
||||||
if threadOrNil?.isMuted == true {
|
|
||||||
// Do nothing
|
|
||||||
} else {
|
|
||||||
performOperation(.subscribe, for: closedGroupPublicKey, publicKey: publicKey)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,12 @@ public extension Notification.Name {
|
||||||
|
|
||||||
static let groupThreadUpdated = Notification.Name("groupThreadUpdated")
|
static let groupThreadUpdated = Notification.Name("groupThreadUpdated")
|
||||||
static let muteSettingUpdated = Notification.Name("muteSettingUpdated")
|
static let muteSettingUpdated = Notification.Name("muteSettingUpdated")
|
||||||
|
static let messageSentStatusDidChange = Notification.Name("messageSentStatusDidChange")
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public extension NSNotification {
|
@objc public extension NSNotification {
|
||||||
|
|
||||||
@objc static let groupThreadUpdated = Notification.Name.groupThreadUpdated.rawValue as NSString
|
@objc static let groupThreadUpdated = Notification.Name.groupThreadUpdated.rawValue as NSString
|
||||||
@objc static let muteSettingUpdated = Notification.Name.muteSettingUpdated.rawValue as NSString
|
@objc static let muteSettingUpdated = Notification.Name.muteSettingUpdated.rawValue as NSString
|
||||||
|
@objc static let messageSentStatusDidChange = Notification.Name.messageSentStatusDidChange.rawValue as NSString
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ extern NSString *const TSGroupThread_NotificationKey_UniqueId;
|
||||||
transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||||
|
|
||||||
- (void)setGroupModel:(TSGroupModel *)newGroupModel withTransaction:(YapDatabaseReadWriteTransaction *)transaction;
|
- (void)setGroupModel:(TSGroupModel *)newGroupModel withTransaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||||
- (void)setisOnlyNotifyingForMentions:(BOOL)isOnlyNotifyingForMentions withTransaction:(YapDatabaseReadWriteTransaction *)transaction;
|
- (void)setIsOnlyNotifyingForMentions:(BOOL)isOnlyNotifyingForMentions withTransaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||||
- (void)leaveGroupWithSneakyTransaction;
|
- (void)leaveGroupWithSneakyTransaction;
|
||||||
- (void)leaveGroupWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
|
- (void)leaveGroupWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||||
|
|
||||||
|
|
|
@ -208,7 +208,7 @@ NSString *const TSGroupThread_NotificationKey_UniqueId = @"TSGroupThread_Notific
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setisOnlyNotifyingForMentions:(BOOL)isOnlyNotifyingForMentions withTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
- (void)setIsOnlyNotifyingForMentions:(BOOL)isOnlyNotifyingForMentions withTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||||
{
|
{
|
||||||
self.isOnlyNotifyingForMentions = isOnlyNotifyingForMentions;
|
self.isOnlyNotifyingForMentions = isOnlyNotifyingForMentions;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
/// A message is invalidated when it needs to be re-rendered in the UI. Examples of when this happens include:
|
||||||
|
///
|
||||||
|
/// • When the sent or read status of a message is updated.
|
||||||
|
/// • When an attachment is uploaded or downloaded.
|
||||||
|
@objc public final class MessageInvalidator : NSObject {
|
||||||
|
private static var invalidatedMessages: Set<String> = []
|
||||||
|
|
||||||
|
@objc public static let shared = MessageInvalidator()
|
||||||
|
|
||||||
|
private override init() { }
|
||||||
|
|
||||||
|
@objc public static func invalidate(_ message: TSMessage, with transaction: YapDatabaseReadWriteTransaction) {
|
||||||
|
guard let id = message.uniqueId, !isInvalidated(message) else { return }
|
||||||
|
invalidatedMessages.insert(id)
|
||||||
|
message.touch(with: transaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public static func isInvalidated(_ message: TSMessage) -> Bool {
|
||||||
|
guard let id = message.uniqueId else { return false }
|
||||||
|
return invalidatedMessages.contains(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public static func markAsUpdated(_ id: String) {
|
||||||
|
invalidatedMessages.remove(id)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,8 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>com.apple.developer.usernotifications.filtering</key>
|
||||||
|
<true/>
|
||||||
<key>aps-environment</key>
|
<key>aps-environment</key>
|
||||||
<string>development</string>
|
<string>development</string>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
|
|
|
@ -14,6 +14,7 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension
|
||||||
override public func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
override public func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||||
self.contentHandler = contentHandler
|
self.contentHandler = contentHandler
|
||||||
self.notificationContent = request.content.mutableCopy() as? UNMutableNotificationContent
|
self.notificationContent = request.content.mutableCopy() as? UNMutableNotificationContent
|
||||||
|
let userPublicKey = SNGeneralUtilities.getUserPublicKey()
|
||||||
|
|
||||||
// Abort if the main app is running
|
// Abort if the main app is running
|
||||||
var isMainAppAndActive = false
|
var isMainAppAndActive = false
|
||||||
|
@ -36,6 +37,10 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension
|
||||||
do {
|
do {
|
||||||
let (message, proto) = try MessageReceiver.parse(envelopeAsData, openGroupMessageServerID: nil, using: transaction)
|
let (message, proto) = try MessageReceiver.parse(envelopeAsData, openGroupMessageServerID: nil, using: transaction)
|
||||||
let senderPublicKey = message.sender!
|
let senderPublicKey = message.sender!
|
||||||
|
if (senderPublicKey == userPublicKey) {
|
||||||
|
// Ignore PNs for messages sent by the current user
|
||||||
|
return self.completeSilenty()
|
||||||
|
}
|
||||||
var senderDisplayName = Storage.shared.getContact(with: senderPublicKey)?.displayName(for: .regular) ?? senderPublicKey
|
var senderDisplayName = Storage.shared.getContact(with: senderPublicKey)?.displayName(for: .regular) ?? senderPublicKey
|
||||||
let snippet: String
|
let snippet: String
|
||||||
var userInfo: [String:Any] = [ NotificationServiceExtension.isFromRemoteKey : true ]
|
var userInfo: [String:Any] = [ NotificationServiceExtension.isFromRemoteKey : true ]
|
||||||
|
@ -47,6 +52,7 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension
|
||||||
}
|
}
|
||||||
let thread = tsIncomingMessage.thread(with: transaction)
|
let thread = tsIncomingMessage.thread(with: transaction)
|
||||||
if thread.isMuted {
|
if thread.isMuted {
|
||||||
|
// Ignore PNs if the thread is muted
|
||||||
return self.completeSilenty()
|
return self.completeSilenty()
|
||||||
}
|
}
|
||||||
let threadID = thread.uniqueId!
|
let threadID = thread.uniqueId!
|
||||||
|
@ -56,6 +62,10 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension
|
||||||
if let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction), let group = thread as? TSGroupThread,
|
if let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction), let group = thread as? TSGroupThread,
|
||||||
group.groupModel.groupType == .closedGroup { // Should always be true because we don't get PNs for open groups
|
group.groupModel.groupType == .closedGroup { // Should always be true because we don't get PNs for open groups
|
||||||
senderDisplayName = String(format: NotificationStrings.incomingGroupMessageTitleFormat, senderDisplayName, group.groupModel.groupName ?? MessageStrings.newGroupDefaultTitle)
|
senderDisplayName = String(format: NotificationStrings.incomingGroupMessageTitleFormat, senderDisplayName, group.groupModel.groupName ?? MessageStrings.newGroupDefaultTitle)
|
||||||
|
if group.isOnlyNotifyingForMentions && !tsIncomingMessage.isUserMentioned {
|
||||||
|
// Ignore PNs if the group is set to only notify for mentions
|
||||||
|
return self.completeSilenty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case let closedGroupControlMessage as ClosedGroupControlMessage:
|
case let closedGroupControlMessage as ClosedGroupControlMessage:
|
||||||
// TODO: We could consider actually handling the update here. Not sure if there's enough time though, seeing as though
|
// TODO: We could consider actually handling the update here. Not sure if there's enough time though, seeing as though
|
||||||
|
@ -192,7 +202,7 @@ private extension String {
|
||||||
func replacingMentions(for threadID: String, using transaction: YapDatabaseReadWriteTransaction) -> String {
|
func replacingMentions(for threadID: String, using transaction: YapDatabaseReadWriteTransaction) -> String {
|
||||||
MentionsManager.populateUserPublicKeyCacheIfNeeded(for: threadID, in: transaction)
|
MentionsManager.populateUserPublicKeyCacheIfNeeded(for: threadID, in: transaction)
|
||||||
var result = self
|
var result = self
|
||||||
let regex = try! NSRegularExpression(pattern: "@[0-9a-fA-F]*", options: [])
|
let regex = try! NSRegularExpression(pattern: "@[0-9a-fA-F]{66}", options: [])
|
||||||
let knownPublicKeys = MentionsManager.userPublicKeyCache[threadID] ?? []
|
let knownPublicKeys = MentionsManager.userPublicKeyCache[threadID] ?? []
|
||||||
var mentions: [(range: NSRange, publicKey: String)] = []
|
var mentions: [(range: NSRange, publicKey: String)] = []
|
||||||
var m0 = regex.firstMatch(in: result, options: .withoutAnchoringBounds, range: NSRange(location: 0, length: result.utf16.count))
|
var m0 = regex.firstMatch(in: result, options: .withoutAnchoringBounds, range: NSRange(location: 0, length: result.utf16.count))
|
||||||
|
|
|
@ -72,11 +72,13 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
[LKStorage readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
[LKStorage readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||||
YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName];
|
YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName];
|
||||||
NSArray<NSString *> *allGroups = [unreadMessages allGroups];
|
NSArray<NSString *> *allGroups = [unreadMessages allGroups];
|
||||||
|
// FIXME: Confusingly, `allGroups` includes contact threads as well
|
||||||
for (NSString *groupID in allGroups) {
|
for (NSString *groupID in allGroups) {
|
||||||
TSGroupThread *thread = [TSGroupThread fetchObjectWithUniqueID:groupID transaction:transaction];
|
TSThread *thread = [TSThread fetchObjectWithUniqueID:groupID transaction:transaction];
|
||||||
if (thread.isMuted) continue;
|
if (thread.isMuted) { continue; }
|
||||||
|
BOOL isGroupThread = thread.isGroupThread;
|
||||||
[unreadMessages enumerateKeysAndObjectsInGroup:groupID
|
[unreadMessages enumerateKeysAndObjectsInGroup:groupID
|
||||||
usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) {
|
usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) {
|
||||||
if (![object conformsToProtocol:@protocol(OWSReadTracking)]) {
|
if (![object conformsToProtocol:@protocol(OWSReadTracking)]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -85,9 +87,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
NSLog(@"Found an already read message in the * unread * messages list.");
|
NSLog(@"Found an already read message in the * unread * messages list.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TSIncomingMessage * incomingMessage = (TSIncomingMessage *)object;
|
if ([object isKindOfClass:TSIncomingMessage.class] && isGroupThread) {
|
||||||
if (incomingMessage != nil && thread.isOnlyNotifyingForMentions && !incomingMessage.isUserMentioned) {
|
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)object;
|
||||||
return;
|
if (((TSGroupThread *)thread).isOnlyNotifyingForMentions && !incomingMessage.isUserMentioned) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
count += 1;
|
count += 1;
|
||||||
}];
|
}];
|
||||||
|
|
|
@ -69,7 +69,7 @@ public final class ProfilePictureView : UIView {
|
||||||
publicKey = ""
|
publicKey = ""
|
||||||
useFallbackPicture = true
|
useFallbackPicture = true
|
||||||
} else { // A closed group
|
} else { // A closed group
|
||||||
var users = MentionsManager.userPublicKeyCache[thread.uniqueId!] ?? []
|
var users = Set(thread.groupModel.groupMemberIds)
|
||||||
users.remove(getUserHexEncodedPublicKey())
|
users.remove(getUserHexEncodedPublicKey())
|
||||||
var randomUsers = users.sorted() // Sort to provide a level of stability
|
var randomUsers = users.sorted() // Sort to provide a level of stability
|
||||||
if users.count == 1 {
|
if users.count == 1 {
|
||||||
|
|
Loading…
Reference in New Issue