Merge branch 'various-bugs-and-optimisations' into emoji-reacts

This commit is contained in:
Ryan Zhao 2022-08-22 11:30:06 +10:00
commit c7c92f747c
40 changed files with 723 additions and 308 deletions

View File

@ -137,6 +137,7 @@
7B7CB190270FB2150079FF93 /* MiniCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */; };
7B7CB192271508AD0079FF93 /* CallRingTonePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB191271508AD0079FF93 /* CallRingTonePlayer.swift */; };
7B81682328A4C1210069F315 /* UpdateTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682228A4C1210069F315 /* UpdateTypes.swift */; };
7B81682828B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */; };
7B8D5FC428332600008324D9 /* VisibleMessage+Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */; };
7B93D06A27CF173D00811CB6 /* MessageRequestsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D06927CF173D00811CB6 /* MessageRequestsViewController.swift */; };
7B93D07027CF194000811CB6 /* ConfigurationMessage+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */; };
@ -318,8 +319,6 @@
C32C5DD2256DD9E5003C73A2 /* LRUCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAFD255A580600E217F9 /* LRUCache.swift */; };
C32C5DDB256DD9FF003C73A2 /* ContentProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB68255A580F00E217F9 /* ContentProxy.swift */; };
C32C5E0C256DDAFA003C73A2 /* NSRegularExpression+SSK.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA7A255A57FB00E217F9 /* NSRegularExpression+SSK.swift */; };
C32C5FBB256E0206003C73A2 /* OWSBackgroundTask.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDC1B255A581F00E217F9 /* OWSBackgroundTask.m */; };
C32C5FC4256E0209003C73A2 /* OWSBackgroundTask.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB38255A580B00E217F9 /* OWSBackgroundTask.h */; settings = {ATTRIBUTES = (Public, ); }; };
C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB77255A581000E217F9 /* NSUserDefaults+OWS.m */; };
C32C6018256E07F9003C73A2 /* NSUserDefaults+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB51255A580D00E217F9 /* NSUserDefaults+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33100082558FF6D00070591 /* NewConversationButtonSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */; };
@ -602,7 +601,7 @@
FD09799927FFC1A300936362 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09799827FFC1A300936362 /* Attachment.swift */; };
FD09799B27FFC82D00936362 /* Quote.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09799A27FFC82D00936362 /* Quote.swift */; };
FD09B7E328865FDA00ED0B66 /* HighlightMentionBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09B7E228865FDA00ED0B66 /* HighlightMentionBackgroundView.swift */; };
FD09B7E5288670BB00ED0B66 /* _007_EmojiReacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09B7E4288670BB00ED0B66 /* _007_EmojiReacts.swift */; };
FD09B7E5288670BB00ED0B66 /* _008_EmojiReacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09B7E4288670BB00ED0B66 /* _008_EmojiReacts.swift */; };
FD09B7E7288670FD00ED0B66 /* Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09B7E6288670FD00ED0B66 /* Reaction.swift */; };
FD09C5E2282212B3000CE219 /* JobDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E1282212B3000CE219 /* JobDependencies.swift */; };
FD09C5E628260FF9000CE219 /* MediaGalleryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E528260FF9000CE219 /* MediaGalleryViewModel.swift */; };
@ -667,6 +666,7 @@
FD37EA0F28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0E28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift */; };
FD37EA1128AB34B3003AE748 /* TypedTableAlteration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA1028AB34B3003AE748 /* TypedTableAlteration.swift */; };
FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA1428AB42CB003AE748 /* IdentitySpec.swift */; };
FD37EA1B28ACB51F003AE748 /* _007_HomeQueryOptimisationIndexes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA1A28ACB51F003AE748 /* _007_HomeQueryOptimisationIndexes.swift */; };
FD3AABE928306BBD00E5099A /* ThreadPickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3AABE828306BBD00E5099A /* ThreadPickerViewModel.swift */; };
FD3C905C27E3FBEF00CD579F /* BatchRequestInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905B27E3FBEF00CD579F /* BatchRequestInfoSpec.swift */; };
FD3C906027E410F700CD579F /* FileUploadResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */; };
@ -680,6 +680,8 @@
FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */; };
FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */; };
FD4B200E283492210034334B /* InsetLockableTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4B200D283492210034334B /* InsetLockableTableView.swift */; };
FD52090028AF6153006098F6 /* OWSBackgroundTask.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDC1B255A581F00E217F9 /* OWSBackgroundTask.m */; };
FD52090128AF61BA006098F6 /* OWSBackgroundTask.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB38255A580B00E217F9 /* OWSBackgroundTask.h */; settings = {ATTRIBUTES = (Public, ); }; };
FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */; };
FD5C72F9284F0E880029977D /* MessageReceiver+TypingIndicators.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */; };
FD5C72FB284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */; };
@ -799,7 +801,6 @@
FDCDB8DE2810F73B00352A0C /* Differentiable+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCDB8DD2810F73B00352A0C /* Differentiable+Utilities.swift */; };
FDCDB8E02811007F00352A0C /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCDB8DF2811007F00352A0C /* HomeViewModel.swift */; };
FDCDB8E42817819600352A0C /* (null) in Sources */ = {isa = PBXBuildFile; };
FDCDB8F12817ABE600352A0C /* Optional+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCDB8F02817ABE600352A0C /* Optional+Utilities.swift */; };
FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */; };
FDD250702837199200198BDA /* GarbageCollectionJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */; };
FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; };
@ -1177,6 +1178,7 @@
7B7CB18F270FB2150079FF93 /* MiniCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniCallView.swift; sourceTree = "<group>"; };
7B7CB191271508AD0079FF93 /* CallRingTonePlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallRingTonePlayer.swift; sourceTree = "<group>"; };
7B81682228A4C1210069F315 /* UpdateTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateTypes.swift; sourceTree = "<group>"; };
7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _007_HomeQueryOptimisationIndexes.swift; sourceTree = "<group>"; };
7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+Reaction.swift"; sourceTree = "<group>"; };
7B93D06927CF173D00811CB6 /* MessageRequestsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewController.swift; sourceTree = "<group>"; };
7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = "<group>"; };
@ -1687,7 +1689,7 @@
FD09799827FFC1A300936362 /* Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = "<group>"; };
FD09799A27FFC82D00936362 /* Quote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quote.swift; sourceTree = "<group>"; };
FD09B7E228865FDA00ED0B66 /* HighlightMentionBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightMentionBackgroundView.swift; sourceTree = "<group>"; };
FD09B7E4288670BB00ED0B66 /* _007_EmojiReacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _007_EmojiReacts.swift; sourceTree = "<group>"; };
FD09B7E4288670BB00ED0B66 /* _008_EmojiReacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _008_EmojiReacts.swift; sourceTree = "<group>"; };
FD09B7E6288670FD00ED0B66 /* Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reaction.swift; sourceTree = "<group>"; };
FD09C5E1282212B3000CE219 /* JobDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobDependencies.swift; sourceTree = "<group>"; };
FD09C5E528260FF9000CE219 /* MediaGalleryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryViewModel.swift; sourceTree = "<group>"; };
@ -1728,6 +1730,7 @@
FD37EA0E28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _006_FixHiddenModAdminSupport.swift; sourceTree = "<group>"; };
FD37EA1028AB34B3003AE748 /* TypedTableAlteration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedTableAlteration.swift; sourceTree = "<group>"; };
FD37EA1428AB42CB003AE748 /* IdentitySpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentitySpec.swift; sourceTree = "<group>"; };
FD37EA1A28ACB51F003AE748 /* _007_HomeQueryOptimisationIndexes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _007_HomeQueryOptimisationIndexes.swift; sourceTree = "<group>"; };
FD3AABE828306BBD00E5099A /* ThreadPickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerViewModel.swift; sourceTree = "<group>"; };
FD3C905B27E3FBEF00CD579F /* BatchRequestInfoSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchRequestInfoSpec.swift; sourceTree = "<group>"; };
FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUploadResponseSpec.swift; sourceTree = "<group>"; };
@ -1853,7 +1856,6 @@
FDC6D75F2862B3F600B04575 /* Dependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dependencies.swift; sourceTree = "<group>"; };
FDCDB8DD2810F73B00352A0C /* Differentiable+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Differentiable+Utilities.swift"; sourceTree = "<group>"; };
FDCDB8DF2811007F00352A0C /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = "<group>"; };
FDCDB8F02817ABE600352A0C /* Optional+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Utilities.swift"; sourceTree = "<group>"; };
FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DifferenceKit+Utilities.swift"; sourceTree = "<group>"; };
FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageCollectionJob.swift; sourceTree = "<group>"; };
FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryNavigationController.swift; sourceTree = "<group>"; };
@ -2194,6 +2196,14 @@
path = "Views & Modals";
sourceTree = "<group>";
};
7B81682428B30BEC0069F315 /* Recovered References */ = {
isa = PBXGroup;
children = (
FD37EA1A28ACB51F003AE748 /* _007_HomeQueryOptimisationIndexes.swift */,
);
name = "Recovered References";
sourceTree = "<group>";
};
7B93D06827CF173D00811CB6 /* Message Requests */ = {
isa = PBXGroup;
children = (
@ -3122,8 +3132,6 @@
C38EF2F5255B6DBC007E1867 /* OWSAudioPlayer.h */,
C38EF2F7255B6DBC007E1867 /* OWSAudioPlayer.m */,
C38EF281255B6D84007E1867 /* OWSAudioSession.swift */,
C33FDB38255A580B00E217F9 /* OWSBackgroundTask.h */,
C33FDC1B255A581F00E217F9 /* OWSBackgroundTask.m */,
FDF0B75D280AAF35004C14C5 /* Preferences.swift */,
C38EF2FB255B6DBD007E1867 /* OWSWindowManager.h */,
C38EF306255B6DBE007E1867 /* OWSWindowManager.m */,
@ -3191,7 +3199,6 @@
B8A582AE258C65D000AFD84C /* Networking */,
B8A582AD258C655E00AFD84C /* PromiseKit */,
FD09796527F6B0A800936362 /* Utilities */,
FDCDB8EF2817ABCE00352A0C /* Utilities */,
C3D9E43025676D3D0040E4F3 /* Configuration.swift */,
);
path = SessionUtilitiesKit;
@ -3388,6 +3395,7 @@
D221A08C169C9E5E00537ABF /* Frameworks */,
D221A08A169C9E5E00537ABF /* Products */,
2BADBA206E0B8D297E313FBA /* Pods */,
7B81682428B30BEC0069F315 /* Recovered References */,
);
sourceTree = "<group>";
};
@ -3492,6 +3500,8 @@
FD09797127FAA2F500936362 /* Optional+Utilities.swift */,
FD09797C27FBDB2000936362 /* Notification+Utilities.swift */,
FDF222082818D2B0000A4995 /* NSAttributedString+Utilities.swift */,
C33FDB38255A580B00E217F9 /* OWSBackgroundTask.h */,
C33FDC1B255A581F00E217F9 /* OWSBackgroundTask.m */,
);
path = Utilities;
sourceTree = "<group>";
@ -3531,7 +3541,8 @@
FDF40CDD2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift */,
FD37EA0C28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift */,
FD37EA0E28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift */,
FD09B7E4288670BB00ED0B66 /* _007_EmojiReacts.swift */,
7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */,
FD09B7E4288670BB00ED0B66 /* _008_EmojiReacts.swift */,
);
path = Migrations;
sourceTree = "<group>";
@ -3946,14 +3957,6 @@
path = Models;
sourceTree = "<group>";
};
FDCDB8EF2817ABCE00352A0C /* Utilities */ = {
isa = PBXGroup;
children = (
FDCDB8F02817ABE600352A0C /* Optional+Utilities.swift */,
);
path = Utilities;
sourceTree = "<group>";
};
FDE7214E287E50D50093DF33 /* Scripts */ = {
isa = PBXGroup;
children = (
@ -4068,6 +4071,7 @@
C3C2A67D255388CC00C340D1 /* SessionUtilitiesKit.h in Headers */,
C32C6018256E07F9003C73A2 /* NSUserDefaults+OWS.h in Headers */,
B8856D8D256F1502001CE70E /* UIView+OWS.h in Headers */,
FD52090128AF61BA006098F6 /* OWSBackgroundTask.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -4077,7 +4081,6 @@
files = (
C3C2A6F425539DE700C340D1 /* SessionMessagingKit.h in Headers */,
C32C5C46256DCBB2003C73A2 /* AppReadiness.h in Headers */,
C32C5FC4256E0209003C73A2 /* OWSBackgroundTask.h in Headers */,
FD716E732850647900C96BF4 /* NSData+messagePadding.h in Headers */,
B8856D72256F1421001CE70E /* OWSWindowManager.h in Headers */,
B8856CF7256F105E001CE70E /* OWSAudioPlayer.h in Headers */,
@ -5086,7 +5089,6 @@
FDA8EB10280F8238002B68E5 /* Codable+Utilities.swift in Sources */,
C352A36D2557858E00338F3E /* NSTimer+Proxying.m in Sources */,
FD09797B27FBB25900936362 /* Updatable.swift in Sources */,
FDCDB8F12817ABE600352A0C /* Optional+Utilities.swift in Sources */,
7B7CB192271508AD0079FF93 /* CallRingTonePlayer.swift in Sources */,
C3C2ABD22553C6C900C340D1 /* Data+SecureRandom.swift in Sources */,
FD848B8B283DC509000E298B /* PagedDatabaseObserver.swift in Sources */,
@ -5132,6 +5134,7 @@
FD17D7B827F51ECA00122BE0 /* Migration.swift in Sources */,
FD7728982849E8110018502F /* UITableView+ReusableView.swift in Sources */,
7B0EFDEE274F598600FFAAE7 /* TimestampUtils.swift in Sources */,
FD52090028AF6153006098F6 /* OWSBackgroundTask.m in Sources */,
C32C5DDB256DD9FF003C73A2 /* ContentProxy.swift in Sources */,
C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */,
B8F5F58325EC94A6003BF8D4 /* Collection+Subscripting.swift in Sources */,
@ -5176,6 +5179,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7B81682828B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift in Sources */,
FD86585828507B24008B6CF9 /* NSData+messagePadding.m in Sources */,
FD245C52285065D500B966DD /* SignalAttachment.swift in Sources */,
B8856D08256F10F1001CE70E /* DeviceSleepManager.swift in Sources */,
@ -5212,7 +5216,7 @@
FD245C6A2850666F00B966DD /* FileServerAPI.swift in Sources */,
FDC4386927B4E6B800C60D73 /* String+Utlities.swift in Sources */,
FD716E6628502EE200C96BF4 /* CurrentCallProtocol.swift in Sources */,
FD09B7E5288670BB00ED0B66 /* _007_EmojiReacts.swift in Sources */,
FD09B7E5288670BB00ED0B66 /* _008_EmojiReacts.swift in Sources */,
FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */,
7BFD1A8C2747150E00FB91B9 /* TurnServerInfo.swift in Sources */,
C3D9E3BF25676AD70040E4F3 /* (null) in Sources */,
@ -5312,7 +5316,6 @@
FD245C5C2850660A00B966DD /* ConfigurationMessage.swift in Sources */,
FD5C7301284F0F7A0029977D /* MessageReceiver+UnsendRequests.swift in Sources */,
C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */,
C32C5FBB256E0206003C73A2 /* OWSBackgroundTask.m in Sources */,
FD245C642850664F00B966DD /* Threading.swift in Sources */,
FD848B8D283E0B26000E298B /* MessageInputTypes.swift in Sources */,
C32C5C3D256DCBAF003C73A2 /* AppReadiness.m in Sources */,

View File

@ -697,6 +697,17 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
let wasLoadingMore: Bool = self.isLoadingMore
let wasOffsetCloseToBottom: Bool = self.isCloseToBottom
let numItemsInUpdatedData: [Int] = updatedData.map { $0.elements.count }
let didSwapAllContent: Bool = (updatedData
.first(where: { $0.model == .messages })?
.elements
.contains(where: {
$0.id == self.viewModel.interactionData
.first(where: { $0.model == .messages })?
.elements
.first?
.id
}))
.defaulting(to: false)
let itemChangeInfo: ItemChangeInfo? = {
guard
isInsert,
@ -729,7 +740,7 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
)
}()
guard !isInsert || wasLoadingMore || itemChangeInfo?.isInsertAtTop == true else {
guard !isInsert || itemChangeInfo?.isInsertAtTop == true else {
self.viewModel.updateInteractionData(updatedData)
self.tableView.reloadData()
@ -738,16 +749,27 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
if let focusedInteractionId: Int64 = self.focusedInteractionId {
// If we had a focusedInteractionId then scroll to it (and hide the search
// result bar loading indicator)
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in
let delay: DispatchTime = (didSwapAllContent ?
.now() :
(.now() + .milliseconds(100))
)
DispatchQueue.main.asyncAfter(deadline: delay) { [weak self] in
self?.searchController.resultsBar.stopLoading()
self?.scrollToInteractionIfNeeded(
with: focusedInteractionId,
isAnimated: true,
highlight: (self?.shouldHighlightNextScrollToInteraction == true)
)
if wasLoadingMore {
// Complete page loading
self?.isLoadingMore = false
self?.autoLoadNextPageIfNeeded()
}
}
}
else if wasOffsetCloseToBottom {
else if wasOffsetCloseToBottom && !wasLoadingMore {
// Scroll to the bottom if an interaction was just inserted and we either
// just sent a message or are close enough to the bottom (wait a tiny fraction
// to avoid buggy animation behaviour)
@ -755,6 +777,11 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
self?.scrollToBottom(isAnimated: true)
}
}
else if wasLoadingMore {
// Complete page loading
self.isLoadingMore = false
self.autoLoadNextPageIfNeeded()
}
return
}
@ -764,7 +791,7 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
///
/// Unfortunately the UITableView also does some weird things when updating (where it won't have updated it's internal data until
/// after it performs the next layout); the below code checks a condition on layout and if it passes it calls a closure
if let itemChangeInfo: ItemChangeInfo = itemChangeInfo, (itemChangeInfo.isInsertAtTop || wasLoadingMore) {
if let itemChangeInfo: ItemChangeInfo = itemChangeInfo, itemChangeInfo.isInsertAtTop {
let oldCellHeight: CGFloat = self.tableView.rectForRow(at: itemChangeInfo.oldVisibleIndexPath).height
// The the user triggered the 'scrollToTop' animation (by tapping in the nav bar) then we
@ -798,7 +825,7 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
.rectForRow(at: itemChangeInfo.visibleIndexPath)
.height
let heightDiff: CGFloat = (oldCellHeight - (newTargetHeight ?? oldCellHeight))
self?.tableView.contentOffset.y += (calculatedRowHeights - heightDiff)
}
@ -814,13 +841,36 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
)
}
}
// Complete page loading
self?.isLoadingMore = false
self?.autoLoadNextPageIfNeeded()
}
)
}
else if wasLoadingMore {
if let focusedInteractionId: Int64 = self.focusedInteractionId {
DispatchQueue.main.async { [weak self] in
// If we had a focusedInteractionId then scroll to it (and hide the search
// result bar loading indicator)
self?.searchController.resultsBar.stopLoading()
self?.scrollToInteractionIfNeeded(
with: focusedInteractionId,
isAnimated: true,
highlight: (self?.shouldHighlightNextScrollToInteraction == true)
)
// Complete page loading
self?.isLoadingMore = false
self?.autoLoadNextPageIfNeeded()
}
}
else {
// Complete page loading
self.isLoadingMore = false
self.autoLoadNextPageIfNeeded()
}
}
// Update the messages
self.tableView.reload(
@ -849,13 +899,13 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
// the screen will scroll to the bottom instead of the first unread message
if let focusedInteractionId: Int64 = self.viewModel.focusedInteractionId {
self.scrollToInteractionIfNeeded(with: focusedInteractionId, isAnimated: false, highlight: true)
self.unreadCountView.alpha = self.scrollButton.alpha
}
else {
self.scrollToBottom(isAnimated: false)
}
self.scrollButton.alpha = self.getScrollButtonOpacity()
self.unreadCountView.alpha = self.scrollButton.alpha
self.hasPerformedInitialScroll = true
// Now that the data has loaded we need to check if either of the "load more" sections are
@ -1030,6 +1080,7 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
let scrollButtonOpacity: CGFloat = (self?.getScrollButtonOpacity() ?? 0)
self?.scrollButton.alpha = scrollButtonOpacity
self?.unreadCountView.alpha = scrollButtonOpacity
self?.view.setNeedsLayout()
self?.view.layoutIfNeeded()
@ -1239,6 +1290,7 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
self.scrollToInteractionIfNeeded(
with: lastInteractionId,
position: .bottom,
isJumpingToLastInteraction: true,
isAnimated: true
)
return
@ -1297,7 +1349,7 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
let contentOffsetY = tableView.contentOffset.y
let x = (lastPageTop - ConversationVC.bottomInset - contentOffsetY).clamp(0, .greatestFiniteMagnitude)
let a = 1 / (ConversationVC.scrollButtonFullVisibilityThreshold - ConversationVC.scrollButtonNoVisibilityThreshold)
return a * x
return max(0, min(1, a * x))
}
// MARK: - Search
@ -1408,6 +1460,7 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
func scrollToInteractionIfNeeded(
with interactionId: Int64,
position: UITableView.ScrollPosition = .middle,
isJumpingToLastInteraction: Bool = false,
isAnimated: Bool = true,
highlight: Bool = false
) {
@ -1431,10 +1484,18 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
self.searchController.resultsBar.startLoading()
DispatchQueue.global(qos: .default).async { [weak self] in
self?.viewModel.pagedDataObserver?.load(.untilInclusive(
id: interactionId,
padding: 5
))
if isJumpingToLastInteraction {
self?.viewModel.pagedDataObserver?.load(.jumpTo(
id: interactionId,
paddingForInclusive: 5
))
}
else {
self?.viewModel.pagedDataObserver?.load(.untilInclusive(
id: interactionId,
padding: 5
))
}
}
return
}

View File

@ -214,9 +214,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve
}
// Onion request path countries cache
DispatchQueue.global(qos: .utility).sync {
let _ = IP2Country.shared.populateCacheIfNeeded()
}
IP2Country.shared.populateCacheIfNeededAsync()
}
override func viewWillAppear(_ animated: Bool) {

View File

@ -43,8 +43,7 @@ public class HomeViewModel {
// MARK: - Initialization
init() {
self.state = Storage.shared.read { db in try HomeViewModel.retrieveState(db) }
.defaulting(to: State())
self.state = State()
self.pagedDataObserver = nil
// Note: Since this references self we need to finish initializing before setting it, we
@ -139,14 +138,14 @@ public class HomeViewModel {
}()
)
],
/// **Note:** This `optimisedJoinSQL` value includes the required minimum joins needed for the query
/// **Note:** This `optimisedJoinSQL` value includes the required minimum joins needed for the query but differs
/// from the JOINs that are actually used for performance reasons as the basic logic can be simpler for where it's used
joinSQL: SessionThreadViewModel.optimisedJoinSQL,
filterSQL: SessionThreadViewModel.homeFilterSQL(userPublicKey: userPublicKey),
groupSQL: SessionThreadViewModel.groupSQL,
orderSQL: SessionThreadViewModel.homeOrderSQL,
dataQuery: SessionThreadViewModel.baseQuery(
userPublicKey: userPublicKey,
filterSQL: SessionThreadViewModel.homeFilterSQL(userPublicKey: userPublicKey),
groupSQL: SessionThreadViewModel.groupSQL,
orderSQL: SessionThreadViewModel.homeOrderSQL
),
@ -194,8 +193,9 @@ public class HomeViewModel {
let hasHiddenMessageRequests: Bool = db[.hasHiddenMessageRequests]
let userProfile: Profile = Profile.fetchOrCreateCurrentUser(db)
let unreadMessageRequestThreadCount: Int = try SessionThread
.unreadMessageRequestsThreadIdQuery(userPublicKey: userProfile.id)
.fetchCount(db)
.unreadMessageRequestsCountQuery(userPublicKey: userProfile.id)
.fetchOne(db)
.defaulting(to: 0)
return State(
showViewedSeedBanner: !hasViewedSeed,
@ -219,7 +219,8 @@ public class HomeViewModel {
else { return }
/// **MUST** have the same logic as in the 'PagedDataObserver.onChangeUnsorted' above
let currentData: [SessionThreadViewModel] = self.threadData.flatMap { $0.elements }
let currentData: [SessionThreadViewModel] = (self.unobservedThreadDataChanges ?? self.threadData)
.flatMap { $0.elements }
let updatedThreadData: [SectionModel] = self.process(data: currentData, for: currentPageInfo)
guard let onThreadChange: (([SectionModel]) -> ()) = self.onThreadChange else {

View File

@ -86,14 +86,14 @@ public class MessageRequestsViewModel {
}()
)
],
/// **Note:** This `optimisedJoinSQL` value includes the required minimum joins needed for the query
/// **Note:** This `optimisedJoinSQL` value includes the required minimum joins needed for the query but differs
/// from the JOINs that are actually used for performance reasons as the basic logic can be simpler for where it's used
joinSQL: SessionThreadViewModel.optimisedJoinSQL,
filterSQL: SessionThreadViewModel.messageRequestsFilterSQL(userPublicKey: userPublicKey),
groupSQL: SessionThreadViewModel.groupSQL,
orderSQL: SessionThreadViewModel.messageRequetsOrderSQL,
dataQuery: SessionThreadViewModel.baseQuery(
userPublicKey: userPublicKey,
filterSQL: SessionThreadViewModel.messageRequestsFilterSQL(userPublicKey: userPublicKey),
groupSQL: SessionThreadViewModel.groupSQL,
orderSQL: SessionThreadViewModel.messageRequetsOrderSQL
),

View File

@ -367,7 +367,7 @@ public class MediaGalleryViewModel {
.removeDuplicates()
}
@discardableResult public func loadAndCacheAlbumData(for interactionId: Int64) -> [Item] {
@discardableResult public func loadAndCacheAlbumData(for interactionId: Int64, in threadId: String) -> [Item] {
typealias AlbumInfo = (albumData: [Item], interactionIdBefore: Int64?, interactionIdAfter: Int64?)
// Note: It's possible we already have cached album data for this interaction
@ -394,13 +394,19 @@ public class MediaGalleryViewModel {
let itemBefore: Item? = try Item
.baseQuery(
orderSQL: Item.galleryReverseOrderSQL,
customFilters: SQL("\(interaction[.timestampMs]) > \(albumTimestampMs)")
customFilters: SQL("""
\(interaction[.timestampMs]) > \(albumTimestampMs) AND
\(interaction[.threadId]) = \(threadId)
""")
)
.fetchOne(db)
let itemAfter: Item? = try Item
.baseQuery(
orderSQL: Item.galleryOrderSQL,
customFilters: SQL("\(interaction[.timestampMs]) < \(albumTimestampMs)")
customFilters: SQL("""
\(interaction[.timestampMs]) < \(albumTimestampMs) AND
\(interaction[.threadId]) = \(threadId)
""")
)
.fetchOne(db)
@ -505,7 +511,7 @@ public class MediaGalleryViewModel {
threadVariant: threadVariant,
isPagedData: false
)
viewModel.loadAndCacheAlbumData(for: interactionId)
viewModel.loadAndCacheAlbumData(for: interactionId, in: threadId)
viewModel.replaceAlbumObservation(toObservationFor: interactionId)
guard

View File

@ -681,10 +681,15 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
}
// Then check if there is an interaction before the current album interaction
guard let interactionIdAfter: Int64 = self.viewModel.interactionIdAfter[interactionId] else { return nil }
guard let interactionIdAfter: Int64 = self.viewModel.interactionIdAfter[interactionId] else {
return nil
}
// Cache and retrieve the new album items
let newAlbumItems: [MediaGalleryViewModel.Item] = viewModel.loadAndCacheAlbumData(for: interactionIdAfter)
let newAlbumItems: [MediaGalleryViewModel.Item] = viewModel.loadAndCacheAlbumData(
for: interactionIdAfter,
in: self.viewModel.threadId
)
guard
!newAlbumItems.isEmpty,
@ -723,10 +728,15 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
}
// Then check if there is an interaction before the current album interaction
guard let interactionIdBefore: Int64 = self.viewModel.interactionIdBefore[interactionId] else { return nil }
guard let interactionIdBefore: Int64 = self.viewModel.interactionIdBefore[interactionId] else {
return nil
}
// Cache and retrieve the new album items
let newAlbumItems: [MediaGalleryViewModel.Item] = viewModel.loadAndCacheAlbumData(for: interactionIdBefore)
let newAlbumItems: [MediaGalleryViewModel.Item] = viewModel.loadAndCacheAlbumData(
for: interactionIdBefore,
in: self.viewModel.threadId
)
guard
!newAlbumItems.isEmpty,

View File

@ -133,10 +133,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// NOTE: Fix an edge case where user taps on the callkit notification
// but answers the call on another device
stopPollers(shouldStopUserPoller: !self.hasIncomingCallWaiting())
JobRunner.stopAndClearPendingJobs()
// Suspend database
NotificationCenter.default.post(name: Database.suspendNotification, object: self)
// Stop all jobs except for message sending and when completed suspend the database
JobRunner.stopAndClearPendingJobs(exceptForVariant: .messageSend) {
NotificationCenter.default.post(name: Database.suspendNotification, object: self)
}
}
func applicationDidReceiveMemoryWarning(_ application: UIApplication) {

View File

@ -44,7 +44,6 @@
#import <SessionUtilitiesKit/NSData+Image.h>
#import <SessionUtilitiesKit/NSNotificationCenter+OWS.h>
#import <SessionUtilitiesKit/NSString+SSK.h>
#import <SessionMessagingKit/OWSBackgroundTask.h>
#import <SignalUtilitiesKit/OWSDispatch.h>
#import <SignalUtilitiesKit/OWSError.h>
#import <SessionUtilitiesKit/OWSFileSystem.h>

View File

@ -82,10 +82,6 @@ extension AppNotificationAction {
}
}
// Delay notification of incoming messages when it's a background polling to
// avoid too many notifications fired at the same time
let kNotificationDelayForBackgroumdPoll: TimeInterval = 5
let kAudioNotificationsThrottleCount = 2
let kAudioNotificationsThrottleInterval: TimeInterval = 5
@ -93,14 +89,48 @@ protocol NotificationPresenterAdaptee: AnyObject {
func registerNotificationSettings() -> Promise<Void>
func notify(category: AppNotificationCategory, title: String?, body: String, userInfo: [AnyHashable: Any], sound: Preferences.Sound?)
func notify(category: AppNotificationCategory, title: String?, body: String, userInfo: [AnyHashable: Any], sound: Preferences.Sound?, replacingIdentifier: String?)
func notify(
category: AppNotificationCategory,
title: String?,
body: String,
userInfo: [AnyHashable: Any],
previewType: Preferences.NotificationPreviewType,
sound: Preferences.Sound?,
threadVariant: SessionThread.Variant,
threadName: String,
replacingIdentifier: String?
)
func cancelNotifications(threadId: String)
func cancelNotifications(identifiers: [String])
func clearAllNotifications()
}
extension NotificationPresenterAdaptee {
func notify(
category: AppNotificationCategory,
title: String?,
body: String,
userInfo: [AnyHashable: Any],
previewType: Preferences.NotificationPreviewType,
sound: Preferences.Sound?,
threadVariant: SessionThread.Variant,
threadName: String
) {
notify(
category: category,
title: title,
body: body,
userInfo: userInfo,
previewType: previewType,
sound: sound,
threadVariant: threadVariant,
threadName: threadName,
replacingIdentifier: nil
)
}
}
@objc(OWSNotificationPresenter)
public class NotificationPresenter: NSObject, NotificationsProtocol {
@ -141,7 +171,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
return adaptee.registerNotificationSettings()
}
public func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread, isBackgroundPoll: Bool) {
public func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread) {
let isMessageRequest: Bool = thread.isMessageRequest(db, includeNonVisible: true)
// Ensure we should be showing a notification for the thread
@ -149,7 +179,10 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
return
}
let identifier: String = interaction.notificationIdentifier(isBackgroundPoll: isBackgroundPoll)
// Try to group notifications for interactions from open groups
let identifier: String = interaction.notificationIdentifier(
shouldGroupMessagesForThread: (thread.variant == .openGroup)
)
// While batch processing, some of the necessary changes have not been commited.
let rawMessageText = interaction.previewText(db)
@ -166,6 +199,18 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
let senderName = Profile.displayName(db, id: interaction.authorId, threadVariant: thread.variant)
let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType]
.defaulting(to: .nameAndPreview)
let groupName: String = SessionThread.displayName(
threadId: thread.id,
variant: thread.variant,
closedGroupName: try? thread.closedGroup
.select(.name)
.asRequest(of: String.self)
.fetchOne(db),
openGroupName: try? thread.openGroup
.select(.name)
.asRequest(of: String.self)
.fetchOne(db)
)
switch previewType {
case .noNameNoPreview:
@ -177,26 +222,10 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
notificationTitle = (isMessageRequest ? "Session" : senderName)
case .closedGroup, .openGroup:
let groupName: String = SessionThread
.displayName(
threadId: thread.id,
variant: thread.variant,
closedGroupName: try? thread.closedGroup
.select(.name)
.asRequest(of: String.self)
.fetchOne(db),
openGroupName: try? thread.openGroup
.select(.name)
.asRequest(of: String.self)
.fetchOne(db)
)
notificationTitle = (isBackgroundPoll ? groupName :
String(
format: NotificationStrings.incomingGroupMessageTitleFormat,
senderName,
groupName
)
notificationTitle = String(
format: NotificationStrings.incomingGroupMessageTitleFormat,
senderName,
groupName
)
}
}
@ -243,9 +272,12 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
self.adaptee.notify(
category: category,
title: notificationTitle,
body: notificationBody ?? "",
body: (notificationBody ?? ""),
userInfo: userInfo,
previewType: previewType,
sound: sound,
threadVariant: thread.variant,
threadName: groupName,
replacingIdentifier: identifier
)
}
@ -268,23 +300,26 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
guard messageInfo.state == .missed || messageInfo.state == .permissionDenied else { return }
let category = AppNotificationCategory.errorMessage
let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType]
.defaulting(to: .nameAndPreview)
let userInfo = [
AppNotificationUserInfoKey.threadId: thread.id
]
let notificationTitle = interaction.previewText(db)
let notificationTitle: String = interaction.previewText(db)
let threadName: String = SessionThread.displayName(
threadId: thread.id,
variant: thread.variant,
closedGroupName: nil, // Not supported
openGroupName: nil // Not supported
)
var notificationBody: String?
if messageInfo.state == .permissionDenied {
notificationBody = String(
format: "modal_call_missed_tips_explanation".localized(),
SessionThread.displayName(
threadId: thread.id,
variant: thread.variant,
closedGroupName: nil, // Not supported
openGroupName: nil // Not supported
)
threadName
)
}
@ -294,9 +329,12 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
self.adaptee.notify(
category: category,
title: notificationTitle,
body: notificationBody ?? "",
body: (notificationBody ?? ""),
userInfo: userInfo,
previewType: previewType,
sound: sound,
threadVariant: thread.variant,
threadName: threadName,
replacingIdentifier: UUID().uuidString
)
}
@ -328,6 +366,13 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
let userInfo = [
AppNotificationUserInfoKey.threadId: thread.id
]
let threadName: String = SessionThread.displayName(
threadId: thread.id,
variant: thread.variant,
closedGroupName: nil, // Not supported
openGroupName: nil // Not supported
)
DispatchQueue.main.async {
let sound = self.requestSound(thread: thread)
@ -337,7 +382,10 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
title: notificationTitle,
body: notificationBody,
userInfo: userInfo,
previewType: previewType,
sound: sound,
threadVariant: thread.variant,
threadName: threadName,
replacingIdentifier: UUID().uuidString
)
}
@ -347,24 +395,24 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
let notificationTitle: String?
let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType]
.defaulting(to: .nameAndPreview)
let threadName: String = SessionThread.displayName(
threadId: thread.id,
variant: thread.variant,
closedGroupName: try? thread.closedGroup
.select(.name)
.asRequest(of: String.self)
.fetchOne(db),
openGroupName: try? thread.openGroup
.select(.name)
.asRequest(of: String.self)
.fetchOne(db),
isNoteToSelf: (thread.isNoteToSelf(db) == true),
profile: try? Profile.fetchOne(db, id: thread.id)
)
switch previewType {
case .noNameNoPreview: notificationTitle = nil
case .nameNoPreview, .nameAndPreview:
notificationTitle = SessionThread.displayName(
threadId: thread.id,
variant: thread.variant,
closedGroupName: try? thread.closedGroup
.select(.name)
.asRequest(of: String.self)
.fetchOne(db),
openGroupName: try? thread.openGroup
.select(.name)
.asRequest(of: String.self)
.fetchOne(db),
isNoteToSelf: (thread.isNoteToSelf(db) == true),
profile: try? Profile.fetchOne(db, id: thread.id)
)
case .nameNoPreview, .nameAndPreview: notificationTitle = threadName
}
let notificationBody = NotificationStrings.failedToSendBody
@ -381,7 +429,10 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
title: notificationTitle,
body: notificationBody,
userInfo: userInfo,
sound: sound
previewType: previewType,
sound: sound,
threadVariant: thread.variant,
threadName: threadName
)
}
}

View File

@ -57,8 +57,9 @@ class UserNotificationPresenterAdaptee: NSObject, UNUserNotificationCenterDelega
override init() {
self.notificationCenter = UNUserNotificationCenter.current()
super.init()
notificationCenter.delegate = self
SwiftSingletons.register(self)
}
}
@ -86,29 +87,37 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee {
}
}
func notify(category: AppNotificationCategory, title: String?, body: String, userInfo: [AnyHashable: Any], sound: Preferences.Sound?) {
AssertIsOnMainThread()
notify(category: category, title: title, body: body, userInfo: userInfo, sound: sound, replacingIdentifier: nil)
}
func notify(category: AppNotificationCategory, title: String?, body: String, userInfo: [AnyHashable: Any], sound: Preferences.Sound?, replacingIdentifier: String?) {
func notify(
category: AppNotificationCategory,
title: String?,
body: String,
userInfo: [AnyHashable: Any],
previewType: Preferences.NotificationPreviewType,
sound: Preferences.Sound?,
threadVariant: SessionThread.Variant,
threadName: String,
replacingIdentifier: String?
) {
AssertIsOnMainThread()
let threadIdentifier: String? = (userInfo[AppNotificationUserInfoKey.threadId] as? String)
let content = UNMutableNotificationContent()
content.categoryIdentifier = category.identifier
content.userInfo = userInfo
let isReplacingNotification = replacingIdentifier != nil
var isBackgroudPoll = false
if let threadIdentifier = userInfo[AppNotificationUserInfoKey.threadId] as? String {
content.threadIdentifier = threadIdentifier
isBackgroudPoll = replacingIdentifier == threadIdentifier
}
content.threadIdentifier = (threadIdentifier ?? content.threadIdentifier)
let shouldGroupNotification: Bool = (
threadVariant == .openGroup &&
replacingIdentifier == threadIdentifier
)
let isAppActive = UIApplication.shared.applicationState == .active
if let sound = sound, sound != .none {
content.sound = sound.notificationSound(isQuiet: isAppActive)
}
let notificationIdentifier = isReplacingNotification ? replacingIdentifier! : UUID().uuidString
let notificationIdentifier: String = (replacingIdentifier ?? UUID().uuidString)
let isReplacingNotification: Bool = (notifications[notificationIdentifier] != nil)
var trigger: UNNotificationTrigger?
if shouldPresentNotification(category: category, userInfo: userInfo) {
if let displayableTitle = title?.filterForDisplay {
@ -117,30 +126,50 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee {
if let displayableBody = body.filterForDisplay {
content.body = displayableBody
}
} else {
if shouldGroupNotification {
trigger = UNTimeIntervalNotificationTrigger(
timeInterval: Notifications.delayForGroupedNotifications,
repeats: false
)
let numberExistingNotifications: Int? = notifications[notificationIdentifier]?
.content
.userInfo[AppNotificationUserInfoKey.threadNotificationCounter]
.asType(Int.self)
var numberOfNotifications: Int = (numberExistingNotifications ?? 1)
if numberExistingNotifications != nil {
numberOfNotifications += 1 // Add one for the current notification
content.title = (previewType == .noNameNoPreview ?
content.title :
threadName
)
content.body = String(
format: NotificationStrings.incomingCollapsedMessagesBody,
"\(numberOfNotifications)"
)
}
content.userInfo[AppNotificationUserInfoKey.threadNotificationCounter] = numberOfNotifications
}
}
else {
// Play sound and vibrate, but without a `body` no banner will show.
Logger.debug("supressing notification body")
}
let trigger: UNNotificationTrigger?
if isBackgroudPoll {
trigger = UNTimeIntervalNotificationTrigger(timeInterval: kNotificationDelayForBackgroumdPoll, repeats: false)
let numberOfNotifications: Int
if let lastRequest = notifications[notificationIdentifier], let counter = lastRequest.content.userInfo[AppNotificationUserInfoKey.threadNotificationCounter] as? Int {
numberOfNotifications = counter + 1
content.body = String(format: NotificationStrings.incomingCollapsedMessagesBody, "\(numberOfNotifications)")
} else {
numberOfNotifications = 1
}
content.userInfo[AppNotificationUserInfoKey.threadNotificationCounter] = numberOfNotifications
} else {
trigger = nil
}
let request = UNNotificationRequest(identifier: notificationIdentifier, content: content, trigger: trigger)
let request = UNNotificationRequest(
identifier: notificationIdentifier,
content: content,
trigger: trigger
)
Logger.debug("presenting notification with identifier: \(notificationIdentifier)")
if isReplacingNotification { cancelNotifications(identifiers: [notificationIdentifier]) }
notificationCenter.add(request)
notifications[notificationIdentifier] = request
}
@ -196,7 +225,7 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee {
guard let conversationViewController = UIApplication.shared.frontmostViewController as? ConversationVC else {
return true
}
/// Show notifications for any **other** threads
return (conversationViewController.viewModel.threadData.threadId != notificationThreadId)
}

View File

@ -164,12 +164,14 @@ public final class FullConversationCell: UITableViewCell {
// Unread count view
unreadCountView.addSubview(unreadCountLabel)
unreadCountLabel.setCompressionResistanceHigh()
unreadCountLabel.pin([ VerticalEdge.top, VerticalEdge.bottom ], to: unreadCountView)
unreadCountView.pin(.leading, to: .leading, of: unreadCountLabel, withInset: -4)
unreadCountView.pin(.trailing, to: .trailing, of: unreadCountLabel, withInset: 4)
// Has mention view
hasMentionView.addSubview(hasMentionLabel)
hasMentionLabel.setCompressionResistanceHigh()
hasMentionLabel.pin(to: hasMentionView)
// Label stack view

View File

@ -34,7 +34,7 @@ public final class BackgroundPoller {
poller.stop()
return poller.poll(
isBackgroundPoll: true,
calledFromBackgroundPoller: true,
isBackgroundPollerValid: { BackgroundPoller.isValid },
isPostCapabilitiesRetry: false
)
@ -82,7 +82,7 @@ public final class BackgroundPoller {
groupPublicKey,
on: DispatchQueue.main,
maxRetryCount: 0,
isBackgroundPoll: true,
calledFromBackgroundPoller: true,
isBackgroundPollValid: { BackgroundPoller.isValid }
)
}
@ -134,7 +134,7 @@ public final class BackgroundPoller {
threadId: threadId,
details: MessageReceiveJob.Details(
messages: threadMessages.map { $0.messageInfo },
isBackgroundPoll: true
calledFromBackgroundPoller: true
)
)

View File

@ -50,8 +50,8 @@ final class IP2Country {
@objc func populateCacheIfNeededAsync() {
// This has to be sync since the `countryNamesCache` dict doesn't like async access
IP2Country.workQueue.sync {
let _ = self.populateCacheIfNeeded()
IP2Country.workQueue.sync { [weak self] in
_ = self?.populateCacheIfNeeded()
}
}

View File

@ -18,10 +18,9 @@ public enum SNMessagingKit { // Just to make the external API nice
],
[
_005_FixDeletedMessageReadState.self,
_006_FixHiddenModAdminSupport.self
],
[
_007_EmojiReacts.self
_006_FixHiddenModAdminSupport.self,
_007_HomeQueryOptimisationIndexes.self,
_008_EmojiReacts.self
]
]
)

View File

@ -1250,7 +1250,7 @@ enum _003_YDBToGRDBMigration: Migration {
threadId: processedMessage.threadId,
details: MessageReceiveJob.Details(
messages: [processedMessage.messageInfo],
isBackgroundPoll: legacyJob.isBackgroundPoll
calledFromBackgroundPoller: legacyJob.isBackgroundPoll
)
)?.inserted(db)
}

View File

@ -0,0 +1,37 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit
/// This migration adds an index to the interaction table in order to improve the performance of retrieving the number of unread interactions
enum _007_HomeQueryOptimisationIndexes: Migration {
static let target: TargetMigrations.Identifier = .messagingKit
static let identifier: String = "HomeQueryOptimisationIndexes"
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
try db.create(
index: "interaction_on_wasRead_and_hasMention_and_threadId",
on: Interaction.databaseTableName,
columns: [
Interaction.Columns.wasRead.name,
Interaction.Columns.hasMention.name,
Interaction.Columns.threadId.name
]
)
try db.create(
index: "interaction_on_threadId_and_timestampMs_and_variant",
on: Interaction.databaseTableName,
columns: [
Interaction.Columns.threadId.name,
Interaction.Columns.timestampMs.name,
Interaction.Columns.variant.name
]
)
Storage.update(progress: 1, for: self, in: target) // In case this is the last migration
}
}

View File

@ -5,7 +5,7 @@ import GRDB
import SessionUtilitiesKit
/// This migration adds the new types needed for Emoji Reacts
enum _007_EmojiReacts: Migration {
enum _008_EmojiReacts: Migration {
static let target: TargetMigrations.Identifier = .messagingKit
static let identifier: String = "EmojiReacts"
static let needsConfigSync: Bool = false

View File

@ -262,7 +262,7 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu
self.body = body
self.timestampMs = timestampMs
self.receivedAtTimestampMs = receivedAtTimestampMs
self.wasRead = (wasRead && variant.canBeUnread)
self.wasRead = (wasRead || !variant.canBeUnread)
self.hasMention = hasMention
self.expiresInSeconds = expiresInSeconds
self.expiresStartedAtMs = expiresStartedAtMs
@ -304,7 +304,7 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu
default: return timestampMs
}
}()
self.wasRead = (wasRead && variant.canBeUnread)
self.wasRead = (wasRead || !variant.canBeUnread)
self.hasMention = hasMention
self.expiresInSeconds = expiresInSeconds
self.expiresStartedAtMs = expiresStartedAtMs
@ -409,7 +409,7 @@ public extension Interaction {
body: (body ?? self.body),
timestampMs: (timestampMs ?? self.timestampMs),
receivedAtTimestampMs: self.receivedAtTimestampMs,
wasRead: (wasRead ?? self.wasRead),
wasRead: ((wasRead ?? self.wasRead) || !self.variant.canBeUnread),
hasMention: (hasMention ?? self.hasMention),
expiresInSeconds: (expiresInSeconds ?? self.expiresInSeconds),
expiresStartedAtMs: (expiresStartedAtMs ?? self.expiresStartedAtMs),
@ -453,6 +453,23 @@ public extension Interaction {
)
)
// Clear out any notifications for the interactions we mark as read
Environment.shared?.notificationsManager.wrappedValue?.cancelNotifications(
identifiers: interactionIds
.map { interactionId in
Interaction.notificationIdentifier(
for: interactionId,
threadId: threadId,
shouldGroupMessagesForThread: false
)
}
.appending(Interaction.notificationIdentifier(
for: 0,
threadId: threadId,
shouldGroupMessagesForThread: true
))
)
// If we want to send read receipts then try to add the 'SendReadReceiptsJob'
if trySendReadReceipt {
JobRunner.upsert(
@ -573,18 +590,27 @@ public extension Interaction {
var notificationIdentifiers: [String] {
[
notificationIdentifier(isBackgroundPoll: true),
notificationIdentifier(isBackgroundPoll: false)
notificationIdentifier(shouldGroupMessagesForThread: true),
notificationIdentifier(shouldGroupMessagesForThread: false)
]
}
// MARK: - Functions
func notificationIdentifier(isBackgroundPoll: Bool) -> String {
func notificationIdentifier(shouldGroupMessagesForThread: Bool) -> String {
// When the app is in the background we want the notifications to be grouped to prevent spam
guard !isBackgroundPoll else { return threadId }
return Interaction.notificationIdentifier(
for: (id ?? 0),
threadId: threadId,
shouldGroupMessagesForThread: shouldGroupMessagesForThread
)
}
fileprivate static func notificationIdentifier(for id: Int64, threadId: String, shouldGroupMessagesForThread: Bool) -> String {
// When the app is in the background we want the notifications to be grouped to prevent spam
guard !shouldGroupMessagesForThread else { return threadId }
return "\(threadId)-\(id ?? 0)"
return "\(threadId)-\(id)"
}
func markingAsDeleted() -> Interaction {
@ -598,7 +624,7 @@ public extension Interaction {
body: nil,
timestampMs: timestampMs,
receivedAtTimestampMs: receivedAtTimestampMs,
wasRead: (wasRead && Variant.standardIncomingDeleted.canBeUnread),
wasRead: (wasRead || !Variant.standardIncomingDeleted.canBeUnread),
hasMention: hasMention,
expiresInSeconds: expiresInSeconds,
expiresStartedAtMs: expiresStartedAtMs,

View File

@ -202,23 +202,24 @@ public extension SessionThread {
"""
}
static func unreadMessageRequestsThreadIdQuery(userPublicKey: String, includeNonVisible: Bool = false) -> SQLRequest<String> {
static func unreadMessageRequestsCountQuery(userPublicKey: String, includeNonVisible: Bool = false) -> SQLRequest<Int> {
let thread: TypedTableAlias<SessionThread> = TypedTableAlias()
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
let contact: TypedTableAlias<Contact> = TypedTableAlias()
return """
SELECT \(thread[.id])
FROM \(SessionThread.self)
JOIN \(Interaction.self) ON (
\(interaction[.threadId]) = \(thread[.id]) AND
\(interaction[.wasRead]) = false
SELECT COUNT(DISTINCT id) FROM (
SELECT \(thread[.id]) AS id
FROM \(SessionThread.self)
JOIN \(Interaction.self) ON (
\(interaction[.threadId]) = \(thread[.id]) AND
\(interaction[.wasRead]) = false
)
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])
WHERE (
\(SessionThread.isMessageRequest(userPublicKey: userPublicKey, includeNonVisible: includeNonVisible))
)
)
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])
WHERE (
\(SessionThread.isMessageRequest(userPublicKey: userPublicKey, includeNonVisible: includeNonVisible))
)
GROUP BY \(thread[.id])
"""
}
@ -276,8 +277,8 @@ public extension SessionThread {
// all the other message request threads have been read
if !hasHiddenMessageRequests {
let numUnreadMessageRequestThreads: Int = (try? SessionThread
.unreadMessageRequestsThreadIdQuery(userPublicKey: userPublicKey, includeNonVisible: true)
.fetchCount(db))
.unreadMessageRequestsCountQuery(userPublicKey: userPublicKey, includeNonVisible: true)
.fetchOne(db))
.defaulting(to: 1)
guard numUnreadMessageRequestThreads == 1 else { return false }

View File

@ -37,8 +37,7 @@ public enum MessageReceiveJob: JobExecutor {
db,
message: messageInfo.message,
associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData),
openGroupId: nil,
isBackgroundPoll: details.isBackgroundPoll
openGroupId: nil
)
}
catch {
@ -76,7 +75,7 @@ public enum MessageReceiveJob: JobExecutor {
.with(
details: Details(
messages: remainingMessagesToProcess,
isBackgroundPoll: details.isBackgroundPoll
calledFromBackgroundPoller: details.calledFromBackgroundPoller
)
)
.defaulting(to: job)
@ -164,14 +163,18 @@ extension MessageReceiveJob {
}
public let messages: [MessageInfo]
public let isBackgroundPoll: Bool
private let isBackgroundPoll: Bool
// Renamed variable for clarity (and didn't want to migrate old MessageReceiveJob
// values so didn't rename the original)
public var calledFromBackgroundPoller: Bool { isBackgroundPoll }
public init(
messages: [MessageInfo],
isBackgroundPoll: Bool
calledFromBackgroundPoller: Bool
) {
self.messages = messages
self.isBackgroundPoll = isBackgroundPoll
self.isBackgroundPoll = calledFromBackgroundPoller
}
}
}

View File

@ -6,5 +6,4 @@ FOUNDATION_EXPORT const unsigned char SessionMessagingKitVersionString[];
#import <SessionMessagingKit/AppReadiness.h>
#import <SessionMessagingKit/NSData+messagePadding.h>
#import <SessionMessagingKit/OWSAudioPlayer.h>
#import <SessionMessagingKit/OWSBackgroundTask.h>
#import <SessionMessagingKit/OWSWindowManager.h>

View File

@ -512,7 +512,6 @@ public final class OpenGroupManager: NSObject {
messages: [OpenGroupAPI.Message],
for roomToken: String,
on server: String,
isBackgroundPoll: Bool,
dependencies: OGMDependencies = OGMDependencies()
) {
// Sorting the messages by server ID before importing them fixes an issue where messages
@ -564,7 +563,6 @@ public final class OpenGroupManager: NSObject {
message: messageInfo.message,
associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData),
openGroupId: openGroup.id,
isBackgroundPoll: isBackgroundPoll,
dependencies: dependencies
)
}
@ -619,7 +617,6 @@ public final class OpenGroupManager: NSObject {
messages: [OpenGroupAPI.DirectMessage],
fromOutbox: Bool,
on server: String,
isBackgroundPoll: Bool,
dependencies: OGMDependencies = OGMDependencies()
) {
// Don't need to do anything if we have no messages (it's a valid case)
@ -716,7 +713,6 @@ public final class OpenGroupManager: NSObject {
message: messageInfo.message,
associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData),
openGroupId: nil, // Intentionally nil as they are technically not open group messages
isBackgroundPoll: isBackgroundPoll,
dependencies: dependencies
)
}

View File

@ -12,7 +12,6 @@ extension MessageReceiver {
message: VisibleMessage,
associatedWithProto proto: SNProtoContent,
openGroupId: String?,
isBackgroundPoll: Bool,
dependencies: Dependencies = Dependencies()
) throws -> Int64 {
guard let sender: String = message.sender, let dataMessage = proto.dataMessage else {
@ -290,8 +289,7 @@ extension MessageReceiver {
.notifyUser(
db,
for: interaction,
in: thread,
isBackgroundPoll: isBackgroundPoll
in: thread
)
return interactionId

View File

@ -180,7 +180,6 @@ public enum MessageReceiver {
message: Message,
associatedWithProto proto: SNProtoContent,
openGroupId: String?,
isBackgroundPoll: Bool,
dependencies: SMKDependencies = SMKDependencies()
) throws {
switch message {
@ -206,7 +205,7 @@ public enum MessageReceiver {
try MessageReceiver.handleUnsendRequest(db, message: message)
case let message as CallMessage:
try MessageReceiver.handleCallMessage(db, message: message)
try MessageReceiver.handleCallMessage(db, message: message)
case let message as MessageRequestResponse:
try MessageReceiver.handleMessageRequestResponse(db, message: message, dependencies: dependencies)
@ -216,8 +215,7 @@ public enum MessageReceiver {
db,
message: message,
associatedWithProto: proto,
openGroupId: openGroupId,
isBackgroundPoll: isBackgroundPoll
openGroupId: openGroupId
)
default: fatalError()

View File

@ -4,9 +4,15 @@ import Foundation
import GRDB
public protocol NotificationsProtocol {
func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread, isBackgroundPoll: Bool)
func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread)
func notifyUser(_ db: Database, forIncomingCall interaction: Interaction, in thread: SessionThread)
func notifyUser(_ db: Database, forReaction reaction: Reaction, in thread: SessionThread)
func cancelNotifications(identifiers: [String])
func clearAllNotifications()
}
public enum Notifications {
/// Delay notification of incoming messages when we want to group them (eg. during background polling) to avoid
/// firing too many notifications at the same time
public static let delayForGroupedNotifications: TimeInterval = 5
}

View File

@ -145,7 +145,7 @@ public final class ClosedGroupPoller {
_ groupPublicKey: String,
on queue: DispatchQueue = SessionSnodeKit.Threading.workQueue,
maxRetryCount: UInt = 0,
isBackgroundPoll: Bool = false,
calledFromBackgroundPoller: Bool = false,
isBackgroundPollValid: @escaping (() -> Bool) = { true },
poller: ClosedGroupPoller? = nil
) -> Promise<Void> {
@ -156,7 +156,7 @@ public final class ClosedGroupPoller {
return attempt(maxRetryCount: maxRetryCount, recoveringOn: queue) {
guard
(isBackgroundPoll && isBackgroundPollValid()) ||
(calledFromBackgroundPoller && isBackgroundPollValid()) ||
poller?.isPolling.wrappedValue[groupPublicKey] == true
else { return Promise(error: Error.pollingCanceled) }
@ -178,7 +178,7 @@ public final class ClosedGroupPoller {
return when(resolved: promises)
.then(on: queue) { messageResults -> Promise<Void> in
guard
(isBackgroundPoll && isBackgroundPollValid()) ||
(calledFromBackgroundPoller && isBackgroundPollValid()) ||
poller?.isPolling.wrappedValue[groupPublicKey] == true
else { return Promise.value(()) }
@ -195,7 +195,7 @@ public final class ClosedGroupPoller {
// No need to do anything if there are no messages
guard !allMessages.isEmpty else {
if !isBackgroundPoll {
if !calledFromBackgroundPoller {
SNLog("Received no new messages in closed group with public key: \(groupPublicKey)")
}
return Promise.value(())
@ -221,7 +221,7 @@ public final class ClosedGroupPoller {
// In the background ignore 'SQLITE_ABORT' (it generally means
// the BackgroundPoller has timed out
case DatabaseError.SQLITE_ABORT:
guard !isBackgroundPoll else { break }
guard !calledFromBackgroundPoller else { break }
SNLog("Failed to the database being suspended (running in background with no background task).")
break
@ -241,16 +241,16 @@ public final class ClosedGroupPoller {
threadId: groupPublicKey,
details: MessageReceiveJob.Details(
messages: processedMessages.map { $0.messageInfo },
isBackgroundPoll: isBackgroundPoll
calledFromBackgroundPoller: calledFromBackgroundPoller
)
)
// If we are force-polling then add to the JobRunner so they are persistent and will retry on
// the next app run if they fail but don't let them auto-start
JobRunner.add(db, job: jobToRun, canStartJob: !isBackgroundPoll)
JobRunner.add(db, job: jobToRun, canStartJob: !calledFromBackgroundPoller)
}
if isBackgroundPoll {
if calledFromBackgroundPoller {
// We want to try to handle the receive jobs immediately in the background
promises = promises.appending(
jobToRun.map { job -> Promise<Void> in
@ -278,7 +278,7 @@ public final class ClosedGroupPoller {
}
}
if !isBackgroundPoll {
if !calledFromBackgroundPoller {
promise.catch2 { error in
SNLog("Polling failed for closed group with public key: \(groupPublicKey) due to error: \(error).")
}

View File

@ -67,12 +67,12 @@ extension OpenGroupAPI {
@discardableResult
public func poll(using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()) -> Promise<Void> {
return poll(isBackgroundPoll: false, isPostCapabilitiesRetry: false, using: dependencies)
return poll(calledFromBackgroundPoller: false, isPostCapabilitiesRetry: false, using: dependencies)
}
@discardableResult
public func poll(
isBackgroundPoll: Bool,
calledFromBackgroundPoller: Bool,
isBackgroundPollerValid: @escaping (() -> Bool) = { true },
isPostCapabilitiesRetry: Bool,
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()
@ -107,7 +107,7 @@ extension OpenGroupAPI {
.map(on: OpenGroupAPI.workQueue) { (failureCount, $0) }
}
.done(on: OpenGroupAPI.workQueue) { [weak self] failureCount, response in
guard !isBackgroundPoll || isBackgroundPollerValid() else {
guard !calledFromBackgroundPoller || isBackgroundPollerValid() else {
// If this was a background poll and the background poll is no longer valid
// then just stop
self?.isPolling = false
@ -119,7 +119,6 @@ extension OpenGroupAPI {
self?.handlePollResponse(
response,
failureCount: failureCount,
isBackgroundPoll: isBackgroundPoll,
using: dependencies
)
@ -133,7 +132,7 @@ extension OpenGroupAPI {
seal.fulfill(())
}
.catch(on: OpenGroupAPI.workQueue) { [weak self] error in
guard !isBackgroundPoll || isBackgroundPollerValid() else {
guard !calledFromBackgroundPoller || isBackgroundPollerValid() else {
// If this was a background poll and the background poll is no longer valid
// then just stop
self?.isPolling = false
@ -145,7 +144,8 @@ extension OpenGroupAPI {
// method will always resolve)
self?.updateCapabilitiesAndRetryIfNeeded(
server: server,
isBackgroundPoll: isBackgroundPoll,
calledFromBackgroundPoller: calledFromBackgroundPoller,
isBackgroundPollerValid: isBackgroundPollerValid,
isPostCapabilitiesRetry: isPostCapabilitiesRetry,
error: error
)
@ -186,7 +186,8 @@ extension OpenGroupAPI {
private func updateCapabilitiesAndRetryIfNeeded(
server: String,
isBackgroundPoll: Bool,
calledFromBackgroundPoller: Bool,
isBackgroundPollerValid: @escaping (() -> Bool) = { true },
isPostCapabilitiesRetry: Bool,
error: Error,
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()
@ -233,7 +234,8 @@ extension OpenGroupAPI {
// Regardless of the outcome we can just resolve this
// immediately as it'll handle it's own response
return strongSelf.poll(
isBackgroundPoll: isBackgroundPoll,
calledFromBackgroundPoller: calledFromBackgroundPoller,
isBackgroundPollerValid: isBackgroundPollerValid,
isPostCapabilitiesRetry: true,
using: dependencies
)
@ -251,7 +253,6 @@ extension OpenGroupAPI {
private func handlePollResponse(
_ response: PollResponse,
failureCount: Int64,
isBackgroundPoll: Bool,
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()
) {
let server: String = self.server
@ -440,7 +441,6 @@ extension OpenGroupAPI {
messages: responseBody.compactMap { $0.value },
for: roomToken,
on: server,
isBackgroundPoll: isBackgroundPoll,
dependencies: dependencies
)
@ -464,7 +464,6 @@ extension OpenGroupAPI {
messages: messages,
fromOutbox: fromOutbox,
on: server,
isBackgroundPoll: isBackgroundPoll,
dependencies: dependencies
)

View File

@ -173,7 +173,7 @@ public final class Poller {
threadId: threadId,
details: MessageReceiveJob.Details(
messages: threadMessages.map { $0.messageInfo },
isBackgroundPoll: false
calledFromBackgroundPoller: false
)
)
)

View File

@ -404,7 +404,6 @@ public extension SessionThreadViewModel {
/// but including this warning just in case there is a discrepancy)
static func baseQuery(
userPublicKey: String,
filterSQL: SQL,
groupSQL: SQL,
orderSQL: SQL
) -> (([Int64]) -> AdaptedFetchRequest<SQLRequest<SessionThreadViewModel>>) {
@ -422,6 +421,7 @@ public extension SessionThreadViewModel {
let interactionAttachment: TypedTableAlias<InteractionAttachment> = TypedTableAlias()
let profile: TypedTableAlias<Profile> = TypedTableAlias()
let interactionTimestampMsColumnLiteral: SQL = SQL(stringLiteral: Interaction.Columns.timestampMs.name)
let profileIdColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.id.name)
let profileNicknameColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.nickname.name)
let profileNameColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.name.name)
@ -466,7 +466,7 @@ public extension SessionThreadViewModel {
\(Interaction.self).\(ViewModel.interactionIdKey),
\(Interaction.self).\(ViewModel.interactionVariantKey),
\(Interaction.self).\(ViewModel.interactionTimestampMsKey),
\(Interaction.self).\(interactionTimestampMsColumnLiteral) AS \(ViewModel.interactionTimestampMsKey),
\(Interaction.self).\(ViewModel.interactionBodyKey),
-- Default to 'sending' assuming non-processed interaction when null
@ -494,7 +494,7 @@ public extension SessionThreadViewModel {
\(interaction[.id]) AS \(ViewModel.interactionIdKey),
\(interaction[.threadId]),
\(interaction[.variant]) AS \(ViewModel.interactionVariantKey),
MAX(\(interaction[.timestampMs])) AS \(ViewModel.interactionTimestampMsKey),
MAX(\(interaction[.timestampMs])) AS \(interactionTimestampMsColumnLiteral),
\(interaction[.body]) AS \(ViewModel.interactionBodyKey),
\(interaction[.authorId]),
\(interaction[.linkPreviewUrl]),
@ -515,7 +515,7 @@ public extension SessionThreadViewModel {
LEFT JOIN \(LinkPreview.self) ON (
\(linkPreview[.url]) = \(interaction[.linkPreviewUrl]) AND
\(SQL("\(linkPreview[.variant]) = \(LinkPreview.Variant.openGroupInvitation)")) AND
\(Interaction.linkPreviewFilterLiteral(timestampColumn: ViewModel.interactionTimestampMsKey))
\(Interaction.linkPreviewFilterLiteral(timestampColumn: interactionTimestampMsColumnLiteral))
)
LEFT JOIN \(InteractionAttachment.self) AS \(firstInteractionAttachmentLiteral) ON (
\(firstInteractionAttachmentLiteral).\(interactionAttachmentAlbumIndexColumnLiteral) = 0 AND
@ -599,12 +599,14 @@ public extension SessionThreadViewModel {
let contact: TypedTableAlias<Contact> = TypedTableAlias()
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
let interactionTimestampMsColumnLiteral: SQL = SQL(stringLiteral: Interaction.Columns.timestampMs.name)
return """
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])
LEFT JOIN (
SELECT
\(interaction[.threadId]),
MAX(\(interaction[.timestampMs])) AS \(ViewModel.interactionTimestampMsKey)
MAX(\(interaction[.timestampMs])) AS \(interactionTimestampMsColumnLiteral)
FROM \(Interaction.self)
WHERE \(SQL("\(interaction[.variant]) != \(Interaction.Variant.standardIncomingDeleted)"))
GROUP BY \(interaction[.threadId])
@ -615,6 +617,7 @@ public extension SessionThreadViewModel {
static func homeFilterSQL(userPublicKey: String) -> SQL {
let thread: TypedTableAlias<SessionThread> = TypedTableAlias()
let contact: TypedTableAlias<Contact> = TypedTableAlias()
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
return """
\(thread[.shouldBeVisible]) = true AND (
@ -625,7 +628,7 @@ public extension SessionThreadViewModel {
) AND (
-- Only show the 'Note to Self' thread if it has an interaction
\(SQL("\(thread[.id]) != \(userPublicKey)")) OR
\(Interaction.self).\(ViewModel.interactionTimestampMsKey) IS NOT NULL
\(interaction[.timestampMs]) IS NOT NULL
)
"""
}
@ -652,14 +655,16 @@ public extension SessionThreadViewModel {
static let homeOrderSQL: SQL = {
let thread: TypedTableAlias<SessionThread> = TypedTableAlias()
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
return SQL("\(thread[.isPinned]) DESC, IFNULL(\(Interaction.self).\(ViewModel.interactionTimestampMsKey), (\(thread[.creationDateTimestamp]) * 1000)) DESC")
return SQL("\(thread[.isPinned]) DESC, IFNULL(\(interaction[.timestampMs]), (\(thread[.creationDateTimestamp]) * 1000)) DESC")
}()
static let messageRequetsOrderSQL: SQL = {
let thread: TypedTableAlias<SessionThread> = TypedTableAlias()
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
return SQL("IFNULL(\(Interaction.self).\(ViewModel.interactionTimestampMsKey), (\(thread[.creationDateTimestamp]) * 1000)) DESC")
return SQL("IFNULL(\(interaction[.timestampMs]), (\(thread[.creationDateTimestamp]) * 1000)) DESC")
}()
}
@ -738,7 +743,7 @@ public extension SessionThreadViewModel {
SUM(\(interaction[.wasRead]) = false) AS \(ViewModel.threadUnreadCountKey)
FROM \(Interaction.self)
GROUP BY \(interaction[.threadId])
WHERE \(SQL("\(interaction[.threadId]) = \(threadId)"))
) AS \(Interaction.self) ON \(interaction[.threadId]) = \(thread[.id])
LEFT JOIN \(Profile.self) AS \(ViewModel.contactProfileKey) ON \(ViewModel.contactProfileKey).\(profileIdColumnLiteral) = \(thread[.id])
@ -752,17 +757,15 @@ public extension SessionThreadViewModel {
LEFT JOIN (
SELECT
\(groupMember[.groupId]),
COUNT(*) AS \(ViewModel.closedGroupUserCountKey)
COUNT(\(groupMember.alias[Column.rowID])) AS \(ViewModel.closedGroupUserCountKey)
FROM \(GroupMember.self)
WHERE (
\(SQL("\(groupMember[.groupId]) = \(threadId)")) AND
\(SQL("\(groupMember[.role]) = \(GroupMember.Role.standard)"))
)
GROUP BY \(groupMember[.groupId])
) AS \(closedGroupUserCountTableLiteral) ON \(SQL("\(closedGroupUserCountTableLiteral).\(groupMemberGroupIdColumnLiteral) = \(threadId)"))
WHERE \(SQL("\(thread[.id]) = \(threadId)"))
GROUP BY \(thread[.id])
"""
return request.adapted { db in

View File

@ -2120,7 +2120,6 @@ class OpenGroupManagerSpec: QuickSpec {
],
for: "testRoom",
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2142,7 +2141,6 @@ class OpenGroupManagerSpec: QuickSpec {
messages: [],
for: "testRoom",
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2182,7 +2180,6 @@ class OpenGroupManagerSpec: QuickSpec {
],
for: "testRoom",
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2215,7 +2212,6 @@ class OpenGroupManagerSpec: QuickSpec {
],
for: "testRoom",
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2230,7 +2226,6 @@ class OpenGroupManagerSpec: QuickSpec {
messages: [testMessage],
for: "testRoom",
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2260,7 +2255,6 @@ class OpenGroupManagerSpec: QuickSpec {
],
for: "testRoom",
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2298,7 +2292,6 @@ class OpenGroupManagerSpec: QuickSpec {
],
for: "testRoom",
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2327,7 +2320,6 @@ class OpenGroupManagerSpec: QuickSpec {
],
for: "testRoom",
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2379,7 +2371,6 @@ class OpenGroupManagerSpec: QuickSpec {
messages: [],
fromOutbox: false,
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2413,7 +2404,6 @@ class OpenGroupManagerSpec: QuickSpec {
messages: [testDirectMessage],
fromOutbox: false,
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2452,7 +2442,6 @@ class OpenGroupManagerSpec: QuickSpec {
messages: [testDirectMessage],
fromOutbox: false,
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2478,7 +2467,6 @@ class OpenGroupManagerSpec: QuickSpec {
messages: [testDirectMessage],
fromOutbox: false,
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2509,7 +2497,6 @@ class OpenGroupManagerSpec: QuickSpec {
messages: [testDirectMessage],
fromOutbox: false,
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2524,7 +2511,6 @@ class OpenGroupManagerSpec: QuickSpec {
messages: [testDirectMessage],
fromOutbox: false,
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2549,7 +2535,6 @@ class OpenGroupManagerSpec: QuickSpec {
],
fromOutbox: false,
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2576,7 +2561,6 @@ class OpenGroupManagerSpec: QuickSpec {
messages: [testDirectMessage],
fromOutbox: true,
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2607,7 +2591,6 @@ class OpenGroupManagerSpec: QuickSpec {
messages: [testDirectMessage],
fromOutbox: true,
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2623,7 +2606,6 @@ class OpenGroupManagerSpec: QuickSpec {
messages: [testDirectMessage],
fromOutbox: true,
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2659,7 +2641,6 @@ class OpenGroupManagerSpec: QuickSpec {
messages: [testDirectMessage],
fromOutbox: true,
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2674,7 +2655,6 @@ class OpenGroupManagerSpec: QuickSpec {
messages: [testDirectMessage],
fromOutbox: true,
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}
@ -2699,7 +2679,6 @@ class OpenGroupManagerSpec: QuickSpec {
],
fromOutbox: true,
on: "testServer",
isBackgroundPoll: false,
dependencies: dependencies
)
}

View File

@ -9,7 +9,7 @@ import SessionMessagingKit
public class NSENotificationPresenter: NSObject, NotificationsProtocol {
private var notifications: [String: UNNotificationRequest] = [:]
public func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread, isBackgroundPoll: Bool) {
public func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread) {
let isMessageRequest: Bool = thread.isMessageRequest(db, includeNonVisible: true)
// Ensure we should be showing a notification for the thread
@ -18,6 +18,12 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol {
}
let senderName: String = Profile.displayName(db, id: interaction.authorId, threadVariant: thread.variant)
let groupName: String = SessionThread.displayName(
threadId: thread.id,
variant: thread.variant,
closedGroupName: (try? thread.closedGroup.fetchOne(db))?.name,
openGroupName: (try? thread.openGroup.fetchOne(db))?.name
)
var notificationTitle: String = senderName
if thread.variant == .closedGroup || thread.variant == .openGroup {
@ -26,22 +32,11 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol {
return
}
notificationTitle = {
let groupName: String = SessionThread.displayName(
threadId: thread.id,
variant: thread.variant,
closedGroupName: (try? thread.closedGroup.fetchOne(db))?.name,
openGroupName: (try? thread.openGroup.fetchOne(db))?.name
)
guard !isBackgroundPoll else { return groupName }
return String(
format: NotificationStrings.incomingGroupMessageTitleFormat,
senderName,
groupName
)
}()
notificationTitle = String(
format: NotificationStrings.incomingGroupMessageTitleFormat,
senderName,
groupName
)
}
let snippet: String = (interaction.previewText(db)
@ -88,21 +83,31 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol {
notificationContent.body = "MESSAGE_REQUESTS_NOTIFICATION".localized()
}
// Add request
let identifier = interaction.notificationIdentifier(isBackgroundPoll: isBackgroundPoll)
// Add request (try to group notifications for interactions from open groups)
let identifier: String = interaction.notificationIdentifier(
shouldGroupMessagesForThread: (thread.variant == .openGroup)
)
var trigger: UNNotificationTrigger?
if isBackgroundPoll {
trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
if thread.variant == .openGroup {
trigger = UNTimeIntervalNotificationTrigger(
timeInterval: Notifications.delayForGroupedNotifications,
repeats: false
)
var numberOfNotifications: Int = (notifications[identifier]?
let numberExistingNotifications: Int? = notifications[identifier]?
.content
.userInfo[NotificationServiceExtension.threadNotificationCounter]
.asType(Int.self))
.defaulting(to: 1)
.asType(Int.self)
var numberOfNotifications: Int = (numberExistingNotifications ?? 1)
if numberOfNotifications > 1 {
if numberExistingNotifications != nil {
numberOfNotifications += 1 // Add one for the current notification
notificationContent.title = (previewType == .noNameNoPreview ?
notificationContent.title :
groupName
)
notificationContent.body = String(
format: NotificationStrings.incomingCollapsedMessagesBody,
"\(numberOfNotifications)"
@ -112,7 +117,11 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol {
notificationContent.userInfo[NotificationServiceExtension.threadNotificationCounter] = numberOfNotifications
}
addNotifcationRequest(identifier: identifier, notificationContent: notificationContent, trigger: trigger)
addNotifcationRequest(
identifier: identifier,
notificationContent: notificationContent,
trigger: trigger
)
}
public func notifyUser(_ db: Database, forIncomingCall interaction: Interaction, in thread: SessionThread) {
@ -163,7 +172,11 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol {
)
}
addNotifcationRequest(identifier: UUID().uuidString, notificationContent: notificationContent, trigger: nil)
addNotifcationRequest(
identifier: UUID().uuidString,
notificationContent: notificationContent,
trigger: nil
)
}
public func notifyUser(_ db: Database, forReaction reaction: Reaction, in thread: SessionThread) {

View File

@ -83,8 +83,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
db,
message: visibleMessage,
associatedWithProto: processedMessage.proto,
openGroupId: (isOpenGroup ? processedMessage.threadId : nil),
isBackgroundPoll: false
openGroupId: (isOpenGroup ? processedMessage.threadId : nil)
)
// Remove the notifications if there is an outgoing messages from a linked device
@ -329,7 +328,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
.defaulting(to: [])
.map { server in
OpenGroupAPI.Poller(for: server)
.poll(isBackgroundPoll: true, isPostCapabilitiesRetry: false)
.poll(calledFromBackgroundPoller: true, isPostCapabilitiesRetry: false)
.timeout(
seconds: 20,
timeoutError: NotificationServiceError.timeout

View File

@ -379,7 +379,8 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
let orderSQL: SQL = self.orderSQL
let dataQuery: ([Int64]) -> AdaptedFetchRequest<SQLRequest<T>> = self.dataQuery
let loadedPage: (data: [T]?, pageInfo: PagedData.PageInfo)? = Storage.shared.read { [weak self] db in
let loadedPage: (data: [T]?, pageInfo: PagedData.PageInfo, failureCallback: (() -> ())?)? = Storage.shared.read { [weak self] db in
typealias QueryInfo = (limit: Int, offset: Int, updatedCacheOffset: Int)
let totalCount: Int = PagedData.totalCount(
db,
tableName: pagedTableName,
@ -387,7 +388,7 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
filterSQL: filterSQL
)
let queryInfo: (limit: Int, offset: Int, updatedCacheOffset: Int)? = {
let (queryInfo, callback): (QueryInfo?, (() -> ())?) = {
switch target {
case .initialPageAround(let targetId):
// If we want to focus on a specific item then we need to find it's index in
@ -404,7 +405,7 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
// If we couldn't find the targetId then just load the first page
guard let targetIndex: Int = maybeIndex else {
return (currentPageInfo.pageSize, 0, 0)
return ((currentPageInfo.pageSize, 0, 0), nil)
}
let updatedOffset: Int = {
@ -421,22 +422,28 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
return (targetIndex - halfPageSize)
}()
return (currentPageInfo.pageSize, updatedOffset, updatedOffset)
return ((currentPageInfo.pageSize, updatedOffset, updatedOffset), nil)
case .pageBefore:
let updatedOffset: Int = max(0, (currentPageInfo.pageOffset - currentPageInfo.pageSize))
return (
currentPageInfo.pageSize,
updatedOffset,
updatedOffset
(
currentPageInfo.pageSize,
updatedOffset,
updatedOffset
),
nil
)
case .pageAfter:
return (
currentPageInfo.pageSize,
(currentPageInfo.pageOffset + currentPageInfo.currentCount),
currentPageInfo.pageOffset
(
currentPageInfo.pageSize,
(currentPageInfo.pageOffset + currentPageInfo.currentCount),
currentPageInfo.pageOffset
),
nil
)
case .untilInclusive(let targetId, let padding):
@ -459,16 +466,19 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
targetIndex < currentPageInfo.pageOffset ||
targetIndex >= cacheCurrentEndIndex
)
else { return nil }
else { return (nil, nil) }
// If the target is before the cached data then load before
if targetIndex < currentPageInfo.pageOffset {
let finalIndex: Int = max(0, (targetIndex - abs(padding)))
return (
(currentPageInfo.pageOffset - finalIndex),
finalIndex,
finalIndex
(
(currentPageInfo.pageOffset - finalIndex),
finalIndex,
finalIndex
),
nil
)
}
@ -477,23 +487,81 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
let finalIndex: Int = min(totalCount, (targetIndex + 1 + abs(padding)))
return (
(finalIndex - cacheCurrentEndIndex),
cacheCurrentEndIndex,
currentPageInfo.pageOffset
(
(finalIndex - cacheCurrentEndIndex),
cacheCurrentEndIndex,
currentPageInfo.pageOffset
),
nil
)
case .jumpTo(let targetId, let paddingForInclusive):
// If we want to focus on a specific item then we need to find it's index in
// the queried data
let maybeIndex: Int? = PagedData.index(
db,
for: targetId,
tableName: pagedTableName,
idColumn: idColumnName,
orderSQL: orderSQL,
filterSQL: filterSQL
)
let cacheCurrentEndIndex: Int = (currentPageInfo.pageOffset + currentPageInfo.currentCount)
// If we couldn't find the targetId or it's already in the cache then do nothing
guard
let targetIndex: Int = maybeIndex.map({ max(0, min(totalCount, $0)) }),
(
targetIndex < currentPageInfo.pageOffset ||
targetIndex >= cacheCurrentEndIndex
)
else { return (nil, nil) }
// If the target id is within a single page of the current cached data
// then trigger the `untilInclusive` behaviour instead
guard
abs(targetIndex - cacheCurrentEndIndex) > currentPageInfo.pageSize ||
abs(targetIndex - currentPageInfo.pageOffset) > currentPageInfo.pageSize
else {
let callback: () -> () = {
self?.load(.untilInclusive(id: targetId, padding: paddingForInclusive))
}
return (nil, callback)
}
// If the targetId is further than 1 pageSize away then discard the current
// cached data and trigger a fresh `initialPageAround`
let callback: () -> () = {
self?.dataCache.mutate { $0 = DataCache() }
self?.associatedRecords.forEach { $0.clearCache(db) }
self?.pageInfo.mutate {
$0 = PagedData.PageInfo(
pageSize: currentPageInfo.pageSize,
pageOffset: 0,
currentCount: 0,
totalCount: 0
)
}
self?.load(.initialPageAround(id: targetId))
}
return (nil, callback)
case .reloadCurrent:
return (
currentPageInfo.currentCount,
currentPageInfo.pageOffset,
currentPageInfo.pageOffset
(
currentPageInfo.currentCount,
currentPageInfo.pageOffset,
currentPageInfo.pageOffset
),
nil
)
}
}()
// If there is no queryOffset then we already have the data we need so
// early-out (may as well update the 'totalCount' since it may be relevant)
guard let queryInfo: (limit: Int, offset: Int, updatedCacheOffset: Int) = queryInfo else {
guard let queryInfo: QueryInfo = queryInfo else {
return (
nil,
PagedData.PageInfo(
@ -501,7 +569,8 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
pageOffset: currentPageInfo.pageOffset,
currentCount: currentPageInfo.currentCount,
totalCount: totalCount
)
),
callback
)
}
@ -540,7 +609,7 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
)
}
return (newData, updatedLimitInfo)
return (newData, updatedLimitInfo, nil)
}
// Unwrap the updated data
@ -554,6 +623,7 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
self.pageInfo.mutate { $0 = updatedPageInfo }
}
self.isLoadingMoreData.mutate { $0 = false }
loadedPage?.failureCallback?()
return
}
@ -651,6 +721,7 @@ public protocol ErasedAssociatedRecord {
pageInfo: PagedData.PageInfo
) -> Bool
@discardableResult func updateCache(_ db: Database, rowIds: [Int64], hasOtherChanges: Bool) -> Bool
func clearCache(_ db: Database)
func updateAssociatedData<O>(to unassociatedCache: DataCache<O>) -> DataCache<O>
}
@ -735,6 +806,7 @@ public enum PagedData {
case pageBefore
case pageAfter
case untilInclusive(id: SQLExpression, padding: Int)
case jumpTo(id: SQLExpression, paddingForInclusive: Int)
case reloadCurrent
}
@ -757,6 +829,13 @@ public enum PagedData {
/// the padding would mean more data should be loaded)
case untilInclusive(id: ID, padding: Int)
/// This will jump to the specified id, loading a page around it and clearing out any
/// data that was previously cached
///
/// **Note:** If the id is within 1 pageSize of the currently cached data then this
/// will behave as per the `untilInclusive(id:padding:)` type
case jumpTo(id: ID, paddingForInclusive: Int)
fileprivate var internalTarget: InternalTarget {
switch self {
case .initialPageAround(let id): return .initialPageAround(id: id.sqlExpression)
@ -764,6 +843,9 @@ public enum PagedData {
case .pageAfter: return .pageAfter
case .untilInclusive(let id, let padding):
return .untilInclusive(id: id.sqlExpression, padding: padding)
case .jumpTo(let id, let paddingForInclusive):
return .jumpTo(id: id.sqlExpression, paddingForInclusive: paddingForInclusive)
}
}
}
@ -1146,6 +1228,10 @@ public class AssociatedRecord<T, PagedType>: ErasedAssociatedRecord where T: Fet
return true
}
public func clearCache(_ db: Database) {
dataCache.mutate { $0 = DataCache() }
}
public func updateAssociatedData<O>(to unassociatedCache: DataCache<O>) -> DataCache<O> {
guard let typedCache: DataCache<PagedType> = unassociatedCache as? DataCache<PagedType> else {
return unassociatedCache

View File

@ -103,6 +103,7 @@ public final class JobRunner {
internal static var executorMap: Atomic<[Job.Variant: JobExecutor.Type]> = Atomic([:])
fileprivate static var perSessionJobsCompleted: Atomic<Set<Int64>> = Atomic([])
private static var hasCompletedInitialBecomeActive: Atomic<Bool> = Atomic(false)
private static var shutdownBackgroundTask: Atomic<OWSBackgroundTask?> = Atomic(nil)
// MARK: - Configuration
@ -222,6 +223,14 @@ public final class JobRunner {
}
public static func appDidBecomeActive() {
// If we have a running "sutdownBackgroundTask" then we want to cancel it as otherwise it
// can result in the database being suspended and us being unable to interact with it at all
shutdownBackgroundTask.mutate {
$0?.cancel()
$0 = nil
}
// Retrieve any jobs which should run when becoming active
let hasCompletedInitialBecomeActive: Bool = JobRunner.hasCompletedInitialBecomeActive.wrappedValue
let jobsToRun: [Job] = Storage.shared
.read { db in
@ -259,9 +268,56 @@ public final class JobRunner {
/// Calling this will clear the JobRunner queues and stop it from running new jobs, any currently executing jobs will continue to run
/// though (this means if we suspend the database it's likely that any currently running jobs will fail to complete and fail to record their
/// failure - they _should_ be picked up again the next time the app is launched)
public static func stopAndClearPendingJobs() {
queues.wrappedValue.values.forEach { queue in
queue.stopAndClearPendingJobs()
public static func stopAndClearPendingJobs(
exceptForVariant: Job.Variant? = nil,
onComplete: (() -> ())? = nil
) {
// Stop all queues except for the one containing the `exceptForVariant`
queues.wrappedValue
.values
.filter { queue -> Bool in
guard let exceptForVariant: Job.Variant = exceptForVariant else { return true }
return !queue.jobVariants.contains(exceptForVariant)
}
.forEach { $0.stopAndClearPendingJobs() }
// Ensure the queue is actually running (if not the trigger the callback immediately)
guard
let exceptForVariant: Job.Variant = exceptForVariant,
let queue: JobQueue = queues.wrappedValue[exceptForVariant],
queue.isRunning.wrappedValue == true
else {
onComplete?()
return
}
let oldQueueDrained: (() -> ())? = queue.onQueueDrained
// Create a backgroundTask to give the queue the chance to properly be drained
shutdownBackgroundTask.mutate {
$0 = OWSBackgroundTask(labelStr: #function) { [weak queue] state in
// If the background task didn't succeed then trigger the onComplete (and hope we have
// enough time to complete it's logic)
guard state != .cancelled else {
queue?.onQueueDrained = oldQueueDrained
return
}
guard state != .success else { return }
onComplete?()
queue?.onQueueDrained = oldQueueDrained
queue?.stopAndClearPendingJobs()
}
}
// Add a callback to be triggered once the queue is drained
queue.onQueueDrained = { [weak queue] in
oldQueueDrained?()
queue?.onQueueDrained = oldQueueDrained
onComplete?()
shutdownBackgroundTask.mutate { $0 = nil }
}
}
@ -370,7 +426,7 @@ private final class JobQueue {
/// The specific types of jobs this queue manages, if this is left empty it will handle all jobs not handled by other queues
fileprivate let jobVariants: [Job.Variant]
private let onQueueDrained: (() -> ())?
fileprivate var onQueueDrained: (() -> ())?
private lazy var internalQueue: DispatchQueue = {
let result: DispatchQueue = DispatchQueue(

View File

@ -15,4 +15,5 @@ FOUNDATION_EXPORT const unsigned char SessionUtilitiesKitVersionString[];
#import <SessionUtilitiesKit/OWSMath.h>
#import <SessionUtilitiesKit/UIImage+OWS.h>
#import <SessionUtilitiesKit/UIView+OWS.h>
#import <SessionUtilitiesKit/OWSBackgroundTask.h>

View File

@ -10,6 +10,7 @@ typedef NS_ENUM(NSUInteger, BackgroundTaskState) {
BackgroundTaskState_Success,
BackgroundTaskState_CouldNotStart,
BackgroundTaskState_Expired,
BackgroundTaskState_Cancelled,
};
typedef void (^BackgroundTaskCompletionBlock)(BackgroundTaskState backgroundTaskState);
@ -57,6 +58,8 @@ typedef void (^BackgroundTaskCompletionBlock)(BackgroundTaskState backgroundTask
+ (OWSBackgroundTask *)backgroundTaskWithLabel:(NSString *)label
completionBlock:(BackgroundTaskCompletionBlock)completionBlock;
- (void)cancel;
@end
NS_ASSUME_NONNULL_END

View File

@ -375,6 +375,31 @@ typedef NSNumber *OWSTaskId;
}
}
- (void)cancel
{
// Make a local copy of this state, since this method is called by `dealloc`.
BackgroundTaskCompletionBlock _Nullable completionBlock;
@synchronized(self)
{
if (!self.taskId) {
return;
}
[OWSBackgroundTaskManager.sharedManager removeTask:self.taskId];
self.taskId = nil;
completionBlock = self.completionBlock;
self.completionBlock = nil;
}
// endBackgroundTask must be called on the main thread.
DispatchMainThreadSafe(^{
if (completionBlock) {
completionBlock(BackgroundTaskState_Cancelled);
}
});
}
- (void)endBackgroundTask
{
// Make a local copy of this state, since this method is called by `dealloc`.

View File

@ -57,10 +57,23 @@ public final class ProfilePictureView: UIView {
return result
}()
private lazy var additionalProfilePlaceholderImageView: UIImageView = {
let result: UIImageView = UIImageView(
image: UIImage(systemName: "person.fill")?.withRenderingMode(.alwaysTemplate)
)
result.translatesAutoresizingMaskIntoConstraints = false
result.contentMode = .scaleAspectFill
result.tintColor = Colors.text
result.isHidden = true
return result
}()
private lazy var additionalImageView: UIImageView = {
let result: UIImageView = UIImageView()
result.translatesAutoresizingMaskIntoConstraints = false
result.contentMode = .scaleAspectFill
result.tintColor = Colors.text
result.isHidden = true
return result
@ -107,11 +120,17 @@ public final class ProfilePictureView: UIView {
imageContainerView.addSubview(animatedImageView)
additionalImageContainerView.addSubview(additionalImageView)
additionalImageContainerView.addSubview(additionalAnimatedImageView)
additionalImageContainerView.addSubview(additionalProfilePlaceholderImageView)
imageView.pin(to: imageContainerView)
animatedImageView.pin(to: imageContainerView)
additionalImageView.pin(to: additionalImageContainerView)
additionalAnimatedImageView.pin(to: additionalImageContainerView)
additionalProfilePlaceholderImageView.pin(.top, to: .top, of: additionalImageContainerView, withInset: 3)
additionalProfilePlaceholderImageView.pin(.left, to: .left, of: additionalImageContainerView)
additionalProfilePlaceholderImageView.pin(.right, to: .right, of: additionalImageContainerView)
additionalProfilePlaceholderImageView.pin(.bottom, to: .bottom, of: additionalImageContainerView, withInset: 3)
}
// FIXME: Remove this once we refactor the OWSConversationSettingsViewController to Swift (use the HomeViewModel approach)
@ -172,6 +191,7 @@ public final class ProfilePictureView: UIView {
additionalAnimatedImageView.image = nil
additionalImageView.isHidden = true
additionalAnimatedImageView.isHidden = true
additionalProfilePlaceholderImageView.isHidden = true
return
}
guard !publicKey.isEmpty || openGroupProfilePictureData != nil else { return }
@ -240,6 +260,12 @@ public final class ProfilePictureView: UIView {
additionalAnimatedImageView.image = animatedImage
additionalImageView.isHidden = (animatedImage != nil)
additionalAnimatedImageView.isHidden = (animatedImage == nil)
additionalProfilePlaceholderImageView.isHidden = true
}
else {
additionalImageView.isHidden = true
additionalAnimatedImageView.isHidden = true
additionalProfilePlaceholderImageView.isHidden = false
}
default:
@ -251,6 +277,7 @@ public final class ProfilePictureView: UIView {
additionalImageView.isHidden = true
additionalAnimatedImageView.image = nil
additionalAnimatedImageView.isHidden = true
additionalProfilePlaceholderImageView.isHidden = true
}
// Set the image

View File

@ -7,7 +7,7 @@ import SessionMessagingKit
public class NoopNotificationsManager: NotificationsProtocol {
public init() {}
public func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread, isBackgroundPoll: Bool) {
public func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread) {
owsFailDebug("")
}