From ec9adfee7fad8766e257f044e337075b7ad1bbc4 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Wed, 15 Dec 2021 16:35:47 +1100 Subject: [PATCH 01/46] remove populate cache on loading home page --- Session/Utilities/MentionUtilities.swift | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/Session/Utilities/MentionUtilities.swift b/Session/Utilities/MentionUtilities.swift index f0f5be6c8..fb0fb0d82 100644 --- a/Session/Utilities/MentionUtilities.swift +++ b/Session/Utilities/MentionUtilities.swift @@ -10,27 +10,19 @@ public final class MentionUtilities : NSObject { @objc public static func highlightMentions(in string: String, isOutgoingMessage: Bool, threadID: String, attributes: [NSAttributedString.Key:Any]) -> NSAttributedString { let openGroupV2 = Storage.shared.getV2OpenGroup(for: threadID) - OWSPrimaryStorage.shared().dbReadConnection.read { transaction in - MentionsManager.populateUserPublicKeyCacheIfNeeded(for: threadID, in: transaction) - } var string = string let regex = try! NSRegularExpression(pattern: "@[0-9a-fA-F]{66}", options: []) - let knownPublicKeys = MentionsManager.userPublicKeyCache[threadID] ?? [] // Should always be populated at this point var mentions: [(range: NSRange, publicKey: String)] = [] var outerMatch = regex.firstMatch(in: string, options: .withoutAnchoringBounds, range: NSRange(location: 0, length: string.utf16.count)) while let match = outerMatch { let publicKey = String((string as NSString).substring(with: match.range).dropFirst()) // Drop the @ let matchEnd: Int - if knownPublicKeys.contains(publicKey) { - let context: Contact.Context = (openGroupV2 != nil) ? .openGroup : .regular - let displayName = Storage.shared.getContact(with: publicKey)?.displayName(for: context) - if let displayName = displayName { - string = (string as NSString).replacingCharacters(in: match.range, with: "@\(displayName)") - mentions.append((range: NSRange(location: match.range.location, length: displayName.utf16.count + 1), publicKey: publicKey)) // + 1 to include the @ - matchEnd = match.range.location + displayName.utf16.count - } else { - matchEnd = match.range.location + match.range.length - } + let context: Contact.Context = (openGroupV2 != nil) ? .openGroup : .regular + let displayName = Storage.shared.getContact(with: publicKey)?.displayName(for: context) + if let displayName = displayName { + string = (string as NSString).replacingCharacters(in: match.range, with: "@\(displayName)") + mentions.append((range: NSRange(location: match.range.location, length: displayName.utf16.count + 1), publicKey: publicKey)) // + 1 to include the @ + matchEnd = match.range.location + displayName.utf16.count } else { matchEnd = match.range.location + match.range.length } From dd6ed7427ab1828f7b3df62c547413356926d3d2 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Wed, 16 Feb 2022 13:27:31 +1100 Subject: [PATCH 02/46] update pods --- Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Podfile.lock b/Podfile.lock index d8af51feb..fb59d482d 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -204,6 +204,6 @@ SPEC CHECKSUMS: YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 7f961dc4934dd213f5a3277af57d54caef7a4442 +PODFILE CHECKSUM: 19ce2820c263e8f3c114817f7ca2da73a9382b6a COCOAPODS: 1.11.2 From f21d142a6c0234a043ff369b97e6cade7d5ecf30 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Wed, 16 Feb 2022 13:52:58 +1100 Subject: [PATCH 03/46] remove useless cache populating in NSE --- .../NotificationServiceExtension.swift | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 133c08736..7ab3327ab 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -223,22 +223,18 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension private extension String { func replacingMentions(for threadID: String, using transaction: YapDatabaseReadWriteTransaction) -> String { - MentionsManager.populateUserPublicKeyCacheIfNeeded(for: threadID, in: transaction) var result = self let regex = try! NSRegularExpression(pattern: "@[0-9a-fA-F]{66}", options: []) - let knownPublicKeys = MentionsManager.userPublicKeyCache[threadID] ?? [] var mentions: [(range: NSRange, publicKey: String)] = [] var m0 = regex.firstMatch(in: result, options: .withoutAnchoringBounds, range: NSRange(location: 0, length: result.utf16.count)) while let m1 = m0 { let publicKey = String((result as NSString).substring(with: m1.range).dropFirst()) // Drop the @ var matchEnd = m1.range.location + m1.range.length - if knownPublicKeys.contains(publicKey) { - let displayName = Storage.shared.getContact(with: publicKey)?.displayName(for: .regular) - if let displayName = displayName { - result = (result as NSString).replacingCharacters(in: m1.range, with: "@\(displayName)") - mentions.append((range: NSRange(location: m1.range.location, length: displayName.utf16.count + 1), publicKey: publicKey)) // + 1 to include the @ - matchEnd = m1.range.location + displayName.utf16.count - } + let displayName = Storage.shared.getContact(with: publicKey, using: transaction)?.displayName(for: .regular) + if let displayName = displayName { + result = (result as NSString).replacingCharacters(in: m1.range, with: "@\(displayName)") + mentions.append((range: NSRange(location: m1.range.location, length: displayName.utf16.count + 1), publicKey: publicKey)) // + 1 to include the @ + matchEnd = m1.range.location + displayName.utf16.count } m0 = regex.firstMatch(in: result, options: .withoutAnchoringBounds, range: NSRange(location: matchEnd, length: result.utf16.count - matchEnd)) } From 37edce9a099680ddd32cdd6db749b3a07fefb6ff Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Wed, 16 Feb 2022 14:43:41 +1100 Subject: [PATCH 04/46] remove duplicated PN register/unregister --- Session/Meta/AppDelegate.m | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Session/Meta/AppDelegate.m b/Session/Meta/AppDelegate.m index 1f31d6604..5219167c1 100644 --- a/Session/Meta/AppDelegate.m +++ b/Session/Meta/AppDelegate.m @@ -584,12 +584,6 @@ static NSTimeInterval launchStartedAt; [self.pushRegistrationManager didReceiveVanillaPushToken:deviceToken]; OWSLogInfo(@"Registering for push notifications with token: %@.", deviceToken); - BOOL isUsingFullAPNs = [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"]; - if (isUsingFullAPNs) { - __unused AnyPromise *promise = [LKPushNotificationAPI registerWithToken:deviceToken hexEncodedPublicKey:self.tsAccountManager.localNumber isForcedUpdate:NO]; - } else { - __unused AnyPromise *promise = [LKPushNotificationAPI unregisterToken:deviceToken]; - } } - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error From 7f8c952c66b9dd18eb5ff889c8f178690d112857 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Wed, 16 Feb 2022 16:18:49 +1100 Subject: [PATCH 05/46] fix notification badge --- .../NotificationServiceExtension.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 7ab3327ab..a52e672ff 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -97,7 +97,7 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension return self.completeSilenty() } notificationContent.userInfo = userInfo - notificationContent.badge = 1 + notificationContent.badge = NSNumber(value: OWSMessageUtils.sharedManager().unreadMessagesCount() + 1) let notificationsPreference = Environment.shared.preferences!.notificationPreviewType() switch notificationsPreference { case .namePreview: From cc1b1e8c51cd00080632e20b67ab7fca0500c189 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 17 Feb 2022 14:55:32 +1100 Subject: [PATCH 06/46] poll for open groups in NSE --- Session.xcodeproj/project.pbxproj | 8 + .../xcshareddata/xcschemes/Session.xcscheme | 2 +- Session/Meta/MainAppContext.m | 3 + Session/Notifications/AppNotifications.swift | 2 +- .../MessageReceiver+Handling.swift | 4 +- .../Notifications/PushNotificationAPI.swift | 2 +- .../NSENotificationPresenter.swift | 110 ++++++++++++++ .../NotificationServiceExtension.swift | 139 ++++++------------ .../PromiseKit/Promise+Timeout.swift | 19 +++ 9 files changed, 188 insertions(+), 101 deletions(-) create mode 100644 SessionNotificationServiceExtension/NSENotificationPresenter.swift create mode 100644 SessionUtilitiesKit/PromiseKit/Promise+Timeout.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index c6ae0a2aa..b934a41cc 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -134,6 +134,8 @@ 76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; }; 76EB054018170B33006006FC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C318170B33006006FC /* AppDelegate.m */; }; 7B1581E2271E743B00848B49 /* OWSSounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E1271E743B00848B49 /* OWSSounds.swift */; }; + 7B1D74AA27BCC16E0030B423 /* NSENotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74A927BCC16E0030B423 /* NSENotificationPresenter.swift */; }; + 7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */; }; 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; }; 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; }; 7B7CB18B270591630079FF93 /* ShareLogsModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18A270591630079FF93 /* ShareLogsModal.swift */; }; @@ -1113,6 +1115,8 @@ 76EB03C318170B33006006FC /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 7ABE4694B110C1BBCB0E46A2 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig"; sourceTree = ""; }; 7B1581E1271E743B00848B49 /* OWSSounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSSounds.swift; sourceTree = ""; }; + 7B1D74A927BCC16E0030B423 /* NSENotificationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSENotificationPresenter.swift; sourceTree = ""; }; + 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+Timeout.swift"; sourceTree = ""; }; 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = ""; }; 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = ""; }; @@ -2069,6 +2073,7 @@ C31C219B255BC92200EC2D66 /* Meta */, 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */, 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */, + 7B1D74A927BCC16E0030B423 /* NSENotificationPresenter.swift */, ); path = SessionNotificationServiceExtension; sourceTree = ""; @@ -2281,6 +2286,7 @@ C3C2A5D32553860900C340D1 /* Promise+Delaying.swift */, C3A7225D2558C38D0043A11F /* Promise+Retaining.swift */, C3C2A5D62553860B00C340D1 /* Promise+Retrying.swift */, + 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */, ); path = PromiseKit; sourceTree = ""; @@ -4412,6 +4418,7 @@ files = ( 7BDCFC08242186E700641C39 /* NotificationServiceExtensionContext.swift in Sources */, 7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */, + 7B1D74AA27BCC16E0030B423 /* NSENotificationPresenter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4605,6 +4612,7 @@ buildActionMask = 2147483647; files = ( C3AABDDF2553ECF00042FF4C /* Array+Description.swift in Sources */, + 7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */, C32C5A47256DB8F0003C73A2 /* ECKeyPair+Hexadecimal.swift in Sources */, C3D9E41525676C320040E4F3 /* Storage.swift in Sources */, C32C5D83256DD5B6003C73A2 /* SSKKeychainStorage.swift in Sources */, diff --git a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme index 3426f0ce1..9c2c819e3 100644 --- a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme +++ b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme @@ -229,7 +229,7 @@ String { + var result = self + let regex = try! NSRegularExpression(pattern: "@[0-9a-fA-F]{66}", options: []) + var mentions: [(range: NSRange, publicKey: String)] = [] + var m0 = regex.firstMatch(in: result, options: .withoutAnchoringBounds, range: NSRange(location: 0, length: result.utf16.count)) + while let m1 = m0 { + let publicKey = String((result as NSString).substring(with: m1.range).dropFirst()) // Drop the @ + var matchEnd = m1.range.location + m1.range.length + let displayName = Storage.shared.getContact(with: publicKey, using: transaction)?.displayName(for: .regular) + if let displayName = displayName { + result = (result as NSString).replacingCharacters(in: m1.range, with: "@\(displayName)") + mentions.append((range: NSRange(location: m1.range.location, length: displayName.utf16.count + 1), publicKey: publicKey)) // + 1 to include the @ + matchEnd = m1.range.location + displayName.utf16.count + } + m0 = regex.firstMatch(in: result, options: .withoutAnchoringBounds, range: NSRange(location: matchEnd, length: result.utf16.count - matchEnd)) + } + return result + } +} + diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index a52e672ff..7ffb87888 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -1,6 +1,7 @@ import UserNotifications import SessionMessagingKit import SignalUtilitiesKit +import PromiseKit public final class NotificationServiceExtension : UNNotificationServiceExtension { private var didPerformSetup = false @@ -8,13 +9,14 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension private var contentHandler: ((UNNotificationContent) -> Void)? private var notificationContent: UNMutableNotificationContent? - private static let isFromRemoteKey = "remote" - private static let threadIdKey = "Signal.AppNotificationsUserInfoKey.threadId" + public static let isFromRemoteKey = "remote" + public static let threadIdKey = "Signal.AppNotificationsUserInfoKey.threadId" + // MARK: Did receive a remote push notification request + override public func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler self.notificationContent = request.content.mutableCopy() as? UNMutableNotificationContent - let userPublicKey = SNGeneralUtilities.getUserPublicKey() // Abort if the main app is running var isMainAppAndActive = false @@ -28,6 +30,12 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension // Handle the push notification AppReadiness.runNowOrWhenAppDidBecomeReady { + let openGorupPollingPromises = self.pollForOpneGorups() + defer { + when(resolved: openGorupPollingPromises).done { _ in + self.completeSilenty() + } + } let notificationContent = self.notificationContent! guard let base64EncodedData = notificationContent.userInfo["ENCRYPTED_DATA"] as! String?, let data = Data(base64Encoded: base64EncodedData), let envelope = try? MessageWrapper.unwrap(data: data), let envelopeAsData = try? envelope.serializedData() else { @@ -36,37 +44,12 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension Storage.write { transaction in // Intentionally capture self do { let (message, proto) = try MessageReceiver.parse(envelopeAsData, openGroupMessageServerID: nil, using: transaction) - let senderPublicKey = message.sender! - var senderDisplayName = Storage.shared.getContact(with: senderPublicKey)?.displayName(for: .regular) ?? senderPublicKey - let snippet: String - var userInfo: [String:Any] = [ NotificationServiceExtension.isFromRemoteKey : true ] switch message { case let visibleMessage as VisibleMessage: - let tsIncomingMessageID = try MessageReceiver.handleVisibleMessage(visibleMessage, associatedWithProto: proto, openGroupID: nil, isBackgroundPoll: false, using: transaction) - guard let tsMessage = TSMessage.fetch(uniqueId: tsIncomingMessageID, transaction: transaction) else { - return self.completeSilenty() - } - let thread = tsMessage.thread(with: transaction) - let threadID = thread.uniqueId! - userInfo[NotificationServiceExtension.threadIdKey] = threadID - snippet = tsMessage.previewText(with: transaction).filterForDisplay?.replacingMentions(for: threadID, using: transaction) - ?? "You've got a new message" - if let tsIncomingMessage = tsMessage as? TSIncomingMessage { - if thread.isMuted { - // Ignore PNs if the thread is muted - return self.completeSilenty() - } - if let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction), let group = thread as? TSGroupThread, - group.groupModel.groupType == .closedGroup { // Should always be true because we don't get PNs for open groups - senderDisplayName = String(format: NotificationStrings.incomingGroupMessageTitleFormat, senderDisplayName, group.groupModel.groupName ?? MessageStrings.newGroupDefaultTitle) - if group.isOnlyNotifyingForMentions && !tsIncomingMessage.isUserMentioned { - // Ignore PNs if the group is set to only notify for mentions - return self.completeSilenty() - } - } - // Store the notification ID for unsend requests to later cancel this notification - tsIncomingMessage.setNotificationIdentifier(request.identifier, transaction: transaction) - } else { + let tsMessageID = try MessageReceiver.handleVisibleMessage(visibleMessage, associatedWithProto: proto, openGroupID: nil, isBackgroundPoll: false, using: transaction) + + // Remove the notificaitons if there is an outgoing messages from a linked device + if let tsMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction), tsMessage.isKind(of: TSOutgoingMessage.self), let threadID = tsMessage.thread(with: transaction).uniqueId { let semaphore = DispatchSemaphore(value: 0) let center = UNUserNotificationCenter.current() center.getDeliveredNotifications { notifications in @@ -77,51 +60,24 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension } semaphore.wait() } - notificationContent.sound = OWSSounds.notificationSound(for: thread).notificationSound(isQuiet: false) + case let unsendRequest as UnsendRequest: MessageReceiver.handleUnsendRequest(unsendRequest, using: transaction) - return self.completeSilenty() case let closedGroupControlMessage as ClosedGroupControlMessage: - // TODO: We could consider actually handling the update here. Not sure if there's enough time though, seeing as though - // in some cases we need to send messages (e.g. our sender key) to a number of other users. - switch closedGroupControlMessage.kind { - case .new(_, let name, _, _, _, _): snippet = "\(senderDisplayName) added you to \(name)" - default: return self.completeSilenty() - } - default: return self.completeSilenty() - } - if (senderPublicKey == userPublicKey) { - // Ignore PNs for messages sent by the current user - // after handling the message. Otherwise the closed - // group self-send messages won't show. - return self.completeSilenty() - } - notificationContent.userInfo = userInfo - notificationContent.badge = NSNumber(value: OWSMessageUtils.sharedManager().unreadMessagesCount() + 1) - let notificationsPreference = Environment.shared.preferences!.notificationPreviewType() - switch notificationsPreference { - case .namePreview: - notificationContent.title = senderDisplayName - notificationContent.body = snippet - case .nameNoPreview: - notificationContent.title = senderDisplayName - notificationContent.body = NotificationStrings.incomingMessageBody - case .noNameNoPreview: - notificationContent.title = "Session" - notificationContent.body = NotificationStrings.incomingMessageBody + MessageReceiver.handleClosedGroupControlMessage(closedGroupControlMessage, using: transaction) default: break } - self.handleSuccess(for: notificationContent) } catch { if let error = error as? MessageReceiver.Error, error.isRetryable { self.handleFailure(for: notificationContent) } - self.completeSilenty() } } } } + // MARK: Setup + private func setUpIfNecessary(completion: @escaping () -> Void) { AssertIsOnMainThread() @@ -148,7 +104,7 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension AppSetup.setupEnvironment( appSpecificSingletonBlock: { - SSKEnvironment.shared.notificationsManager = NoopNotificationsManager() + SSKEnvironment.shared.notificationsManager = NSENotificationPresenter() }, migrationCompletion: { [weak self] in self?.versionMigrationsDidComplete() @@ -159,18 +115,6 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension NotificationCenter.default.addObserver(self, selector: #selector(storageIsReady), name: .StorageIsReady, object: nil) } - override public func serviceExtensionTimeWillExpire() { - // Called just before the extension will be terminated by the system. - // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. - let userInfo: [String:Any] = [ NotificationServiceExtension.isFromRemoteKey : true ] - let notificationContent = self.notificationContent! - notificationContent.userInfo = userInfo - notificationContent.badge = 1 - notificationContent.title = "Session" - notificationContent.body = "You've got a new message" - handleSuccess(for: notificationContent) - } - @objc private func versionMigrationsDidComplete() { AssertIsOnMainThread() @@ -203,8 +147,17 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension AppReadiness.setAppIsReady() } + // MARK: Handle completion + + override public func serviceExtensionTimeWillExpire() { + // Called just before the extension will be terminated by the system. + // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. + completeSilenty() + } + private func completeSilenty() { - contentHandler!(.init()) + SNLog("Complete silenty") + self.contentHandler!(.init()) } private func handleSuccess(for content: UNMutableNotificationContent) { @@ -218,26 +171,20 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension content.userInfo = userInfo contentHandler!(content) } -} - -private extension String { - func replacingMentions(for threadID: String, using transaction: YapDatabaseReadWriteTransaction) -> String { - var result = self - let regex = try! NSRegularExpression(pattern: "@[0-9a-fA-F]{66}", options: []) - var mentions: [(range: NSRange, publicKey: String)] = [] - var m0 = regex.firstMatch(in: result, options: .withoutAnchoringBounds, range: NSRange(location: 0, length: result.utf16.count)) - while let m1 = m0 { - let publicKey = String((result as NSString).substring(with: m1.range).dropFirst()) // Drop the @ - var matchEnd = m1.range.location + m1.range.length - let displayName = Storage.shared.getContact(with: publicKey, using: transaction)?.displayName(for: .regular) - if let displayName = displayName { - result = (result as NSString).replacingCharacters(in: m1.range, with: "@\(displayName)") - mentions.append((range: NSRange(location: m1.range.location, length: displayName.utf16.count + 1), publicKey: publicKey)) // + 1 to include the @ - matchEnd = m1.range.location + displayName.utf16.count - } - m0 = regex.firstMatch(in: result, options: .withoutAnchoringBounds, range: NSRange(location: matchEnd, length: result.utf16.count - matchEnd)) + // MARK: Poll for open groups + private func pollForOpneGorups() -> [Promise] { + var promises: [Promise] = [] + let servers = Set(Storage.shared.getAllV2OpenGroups().values.map { $0.server }) + servers.forEach { server in + let poller = OpenGroupPollerV2(for: server) + let promise = poller.poll().timeout(seconds: 20, timeoutError: NotificationServiceError.timeout) + promises.append(promise) } - return result + return promises + } + + private enum NotificationServiceError: Error { + case timeout } } diff --git a/SessionUtilitiesKit/PromiseKit/Promise+Timeout.swift b/SessionUtilitiesKit/PromiseKit/Promise+Timeout.swift new file mode 100644 index 000000000..e4b843df6 --- /dev/null +++ b/SessionUtilitiesKit/PromiseKit/Promise+Timeout.swift @@ -0,0 +1,19 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import PromiseKit + +public extension Promise { + + func timeout(seconds: TimeInterval, timeoutError: Error) -> Promise { + return Promise { seal in + after(seconds: seconds).done { + seal.reject(timeoutError) + } + self.done { result in + seal.fulfill(result) + }.catch { err in + seal.reject(err) + } + } + } +} From a8fd6d9d18bb56be38d1a1fd71b9b10a283855db Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 17 Feb 2022 15:31:27 +1100 Subject: [PATCH 07/46] fix home screen not reloading when coming into foreground --- .../xcshareddata/xcschemes/Session.xcscheme | 2 +- Session/Home/HomeVC.swift | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme index 9c2c819e3..3426f0ce1 100644 --- a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme +++ b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme @@ -229,7 +229,7 @@ Date: Thu, 17 Feb 2022 16:38:36 +1100 Subject: [PATCH 08/46] move open group polling to background thread --- .../Sending & Receiving/Pollers/OpenGroupPollerV2.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift index d5eee9ad1..b8d3b1eee 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift @@ -23,9 +23,13 @@ public final class OpenGroupPollerV2 : NSObject { guard let strongSelf = self else { return } strongSelf.hasStarted = true strongSelf.timer = Timer.scheduledTimer(withTimeInterval: strongSelf.pollInterval, repeats: true) { _ in - self?.poll().retainUntilComplete() + DispatchQueue.global().async { + self?.poll().retainUntilComplete() + } + } + DispatchQueue.global().async { + strongSelf.poll().retainUntilComplete() } - strongSelf.poll().retainUntilComplete() } } From e8e120666b04b936ade6d79182aeeda2ca167568 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 17 Feb 2022 16:38:52 +1100 Subject: [PATCH 09/46] move message poller to back ground thread --- SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index cfb3463ea..9197e0a9e 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -55,7 +55,9 @@ public final class Poller : NSObject { guard let strongSelf = self, strongSelf.isPolling else { return } Timer.scheduledTimer(withTimeInterval: Poller.retryInterval, repeats: false) { _ in guard let strongSelf = self else { return } - strongSelf.setUpPolling() + DispatchQueue.global().async { + strongSelf.setUpPolling() + } } } } From a86310b0f5bbf4a08226b8559164300a644a1bdd Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 14 Feb 2022 16:34:22 +1100 Subject: [PATCH 10/46] improve global search performance --- .../GlobalSearchViewController.swift | 26 +++---------------- .../Database/OWSPrimaryStorage.m | 1 + .../Utilities/FullTextSearchFinder.swift | 11 +++++++- .../Profile Pictures/ProfilePictureView.swift | 3 ++- 4 files changed, 17 insertions(+), 24 deletions(-) diff --git a/Session/Home/GlobalSearch/GlobalSearchViewController.swift b/Session/Home/GlobalSearch/GlobalSearchViewController.swift index ae6dc757e..4d1691364 100644 --- a/Session/Home/GlobalSearch/GlobalSearchViewController.swift +++ b/Session/Home/GlobalSearch/GlobalSearchViewController.swift @@ -13,6 +13,7 @@ class GlobalSearchViewController: BaseVC, UITableViewDelegate, UITableViewDataSo } } var recentSearchResults: [String] = Array(Storage.shared.getRecentSearchResults().reversed()) + var defaultSearchResults: HomeScreenSearchResultSet = HomeScreenSearchResultSet.noteToSelfOnly var searchResultSet: HomeScreenSearchResultSet = HomeScreenSearchResultSet.empty private var lastSearchText: String? var searcher: FullTextSearcher { @@ -106,30 +107,10 @@ class GlobalSearchViewController: BaseVC, UITableViewDelegate, UITableViewDataSo var refreshTimer: Timer? private func refreshSearchResults() { - - guard !searchResultSet.isEmpty else { - // To avoid incorrectly showing the "no results" state, - // always search immediately if the current result set is empty. - refreshTimer?.invalidate() - refreshTimer = nil - - updateSearchResults(searchText: searchText) - return - } - - if refreshTimer != nil { - // Don't start a new refresh timer if there's already one active. - return - } - refreshTimer?.invalidate() refreshTimer = WeakTimer.scheduledTimer(timeInterval: 0.1, target: self, userInfo: nil, repeats: false) { [weak self] _ in - guard let self = self else { - return - } - + guard let self = self else { return } self.updateSearchResults(searchText: self.searchText) - self.refreshTimer = nil } } @@ -137,7 +118,7 @@ class GlobalSearchViewController: BaseVC, UITableViewDelegate, UITableViewDataSo let searchText = rawSearchText.stripped guard searchText.count > 0 else { - searchResultSet = HomeScreenSearchResultSet.noteToSelfOnly + searchResultSet = defaultSearchResults lastSearchText = nil reloadTableData() return @@ -159,6 +140,7 @@ class GlobalSearchViewController: BaseVC, UITableViewDelegate, UITableViewDataSo self.searchResultSet = results self.isLoading = false self.reloadTableData() + self.refreshTimer = nil }) } diff --git a/SessionMessagingKit/Database/OWSPrimaryStorage.m b/SessionMessagingKit/Database/OWSPrimaryStorage.m index ae897bf48..b4852c07a 100644 --- a/SessionMessagingKit/Database/OWSPrimaryStorage.m +++ b/SessionMessagingKit/Database/OWSPrimaryStorage.m @@ -67,6 +67,7 @@ void VerifyRegistrationsForPrimaryStorage(OWSStorage *storage) [self loadDatabase]; _dbReadPool = [[YapDatabaseConnectionPool alloc] initWithDatabase:self.database]; + _dbReadPool.connectionLimit = 10; // Increase max read connection limit. Default is 3. _dbReadWriteConnection = [self newDatabaseConnection]; _uiDatabaseConnection = [self newDatabaseConnection]; diff --git a/SessionMessagingKit/Utilities/FullTextSearchFinder.swift b/SessionMessagingKit/Utilities/FullTextSearchFinder.swift index 0320aeaf2..78ea59d5d 100644 --- a/SessionMessagingKit/Utilities/FullTextSearchFinder.swift +++ b/SessionMessagingKit/Utilities/FullTextSearchFinder.swift @@ -30,6 +30,8 @@ public class FullTextSearchFinder: NSObject { private static var tsAccountManager: TSAccountManager { return TSAccountManager.sharedInstance() } + + public var newQueryTimestamp: UInt64 = 0 // MARK: - Querying @@ -89,6 +91,13 @@ public class FullTextSearchFinder: NSObject { guard let ext: YapDatabaseFullTextSearchTransaction = ext(transaction: transaction) else { return } + + // HACK: Full text search is too expensive even though we drop the max search results + // to a reasonable number. And the async read can sometimes block a thread and make + // other read threads wait for it to finish. The timestamp is a workaround to ensure + // only one thread can be running the full text search at one time. + let currentQueryTimestamp = NSDate.millisecondTimestamp() + newQueryTimestamp = currentQueryTimestamp let query = FullTextSearchFinder.query(searchText: searchText) @@ -99,7 +108,7 @@ public class FullTextSearchFinder: NSObject { snippetOptions.endMatchText = "" snippetOptions.numberOfTokens = 5 ext.enumerateKeysAndObjects(matching: query, with: snippetOptions) { (snippet: String, _: String, _: String, object: Any, stop: UnsafeMutablePointer) in - guard searchResultCount < maxSearchResults else { + guard searchResultCount < maxSearchResults && currentQueryTimestamp >= self.newQueryTimestamp else { stop.pointee = true return } diff --git a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift index b91303954..e60c6fe5e 100644 --- a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift +++ b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift @@ -82,7 +82,6 @@ public final class ProfilePictureView : UIView { update() } else { // A one-to-one chat let thread = thread as! TSContactThread - hasTappableProfilePicture = OWSProfileManager.shared().profileAvatar(forRecipientId: thread.contactSessionID()) != nil update(for: thread.contactSessionID()) } } @@ -92,8 +91,10 @@ public final class ProfilePictureView : UIView { func getProfilePicture(of size: CGFloat, for publicKey: String) -> UIImage? { guard !publicKey.isEmpty else { return nil } if let profilePicture = OWSProfileManager.shared().profileAvatar(forRecipientId: publicKey) { + hasTappableProfilePicture = true return profilePicture } else { + hasTappableProfilePicture = false // TODO: Pass in context? let displayName = Storage.shared.getContact(with: publicKey)?.name ?? publicKey return Identicon.generatePlaceholderIcon(seed: publicKey, text: displayName, size: size) From 16c9b7793a3cad2c51707b403c2883ec78260172 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 21 Feb 2022 13:27:44 +1100 Subject: [PATCH 11/46] move pollers to background threads --- .../Pollers/ClosedGroupPoller.swift | 18 ++++++++++-------- .../Pollers/OpenGroupPollerV2.swift | 4 ++-- .../Sending & Receiving/Pollers/Poller.swift | 2 +- SessionMessagingKit/Utilities/Threading.swift | 4 ++++ 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index 1142ff4d8..459b6c56b 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -89,14 +89,16 @@ public final class ClosedGroupPoller : NSObject { SNLog("Next poll interval for closed group with public key: \(groupPublicKey) is \(nextPollInterval) s.") timers[groupPublicKey] = Timer.scheduledTimer(withTimeInterval: nextPollInterval, repeats: false) { [weak self] timer in timer.invalidate() - self?.poll(groupPublicKey).done2 { _ in - DispatchQueue.main.async { // Timers don't do well on background queues - self?.pollRecursively(groupPublicKey) - } - }.catch2 { error in - // The error is logged in poll(_:) - DispatchQueue.main.async { // Timers don't do well on background queues - self?.pollRecursively(groupPublicKey) + Threading.closedGroupPollerQueue.async { + self?.poll(groupPublicKey).done2 { _ in + DispatchQueue.main.async { // Timers don't do well on background queues + self?.pollRecursively(groupPublicKey) + } + }.catch2 { error in + // The error is logged in poll(_:) + DispatchQueue.main.async { // Timers don't do well on background queues + self?.pollRecursively(groupPublicKey) + } } } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift index b8d3b1eee..9e50c2717 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift @@ -23,11 +23,11 @@ public final class OpenGroupPollerV2 : NSObject { guard let strongSelf = self else { return } strongSelf.hasStarted = true strongSelf.timer = Timer.scheduledTimer(withTimeInterval: strongSelf.pollInterval, repeats: true) { _ in - DispatchQueue.global().async { + Threading.openGroupPollerQueue.async { self?.poll().retainUntilComplete() } } - DispatchQueue.global().async { + Threading.openGroupPollerQueue.async { strongSelf.poll().retainUntilComplete() } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 9197e0a9e..e0bbe578f 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -55,7 +55,7 @@ public final class Poller : NSObject { guard let strongSelf = self, strongSelf.isPolling else { return } Timer.scheduledTimer(withTimeInterval: Poller.retryInterval, repeats: false) { _ in guard let strongSelf = self else { return } - DispatchQueue.global().async { + Threading.pollerQueue.async { strongSelf.setUpPolling() } } diff --git a/SessionMessagingKit/Utilities/Threading.swift b/SessionMessagingKit/Utilities/Threading.swift index fb4a1dbba..83f34ffd6 100644 --- a/SessionMessagingKit/Utilities/Threading.swift +++ b/SessionMessagingKit/Utilities/Threading.swift @@ -3,4 +3,8 @@ import Foundation internal enum Threading { internal static let jobQueue = DispatchQueue(label: "SessionMessagingKit.jobQueue", qos: .userInitiated) + + internal static let pollerQueue = DispatchQueue(label: "SessionMessagingKit.pollerQueue") + internal static let closedGroupPollerQueue = DispatchQueue(label: "SessionMessagingKit.closedGroupPollerQueue") + internal static let openGroupPollerQueue = DispatchQueue(label: "SessionMessagingKit.openGroup") } From 36907d3af0e818dd85b21904470c09779736c216 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 21 Feb 2022 14:49:47 +1100 Subject: [PATCH 12/46] improve mention and unread message count --- .../Read Tracking/OWSReadReceiptManager.m | 4 ++ SessionMessagingKit/Threads/TSThread.h | 4 +- SessionMessagingKit/Threads/TSThread.m | 62 +++++++------------ .../Messaging/ThreadViewModel.swift | 2 +- 4 files changed, 30 insertions(+), 42 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/Read Tracking/OWSReadReceiptManager.m b/SessionMessagingKit/Sending & Receiving/Read Tracking/OWSReadReceiptManager.m index 13a1b39c2..04829cd4e 100644 --- a/SessionMessagingKit/Sending & Receiving/Read Tracking/OWSReadReceiptManager.m +++ b/SessionMessagingKit/Sending & Receiving/Read Tracking/OWSReadReceiptManager.m @@ -287,6 +287,10 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE for (id readItem in newlyReadList) { [readItem markAsReadAtTimestamp:readTimestamp sendReadReceipt:wasLocal transaction:transaction]; } + + // Update unread mention. + thread.hasUnreadMentionMessage = false; + [thread saveWithTransaction:transaction]; } #pragma mark - Settings diff --git a/SessionMessagingKit/Threads/TSThread.h b/SessionMessagingKit/Threads/TSThread.h index dcf5346c3..26f6a97d6 100644 --- a/SessionMessagingKit/Threads/TSThread.h +++ b/SessionMessagingKit/Threads/TSThread.h @@ -16,6 +16,7 @@ BOOL IsNoteToSelfEnabled(void); */ @interface TSThread : TSYapDatabaseObject +@property (nonatomic) BOOL hasUnreadMentionMessage; @property (nonatomic) BOOL isPinned; @property (nonatomic) BOOL shouldBeVisible; @property (nonatomic, readonly) NSDate *creationDate; @@ -61,9 +62,6 @@ BOOL IsNoteToSelfEnabled(void); - (NSUInteger)unreadMessageCountWithTransaction:(YapDatabaseReadTransaction *)transaction NS_SWIFT_NAME(unreadMessageCount(transaction:)); -- (BOOL)hasUnreadMentionMessageWithTransaction:(YapDatabaseReadTransaction *)transaction - NS_SWIFT_NAME(hasUnreadMentionMessage(transaction:)); - - (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; /** diff --git a/SessionMessagingKit/Threads/TSThread.m b/SessionMessagingKit/Threads/TSThread.m index 94402dc2f..ecc9c7a04 100644 --- a/SessionMessagingKit/Threads/TSThread.m +++ b/SessionMessagingKit/Threads/TSThread.m @@ -254,46 +254,22 @@ BOOL IsNoteToSelfEnabled(void) __block NSUInteger count = 0; YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName]; - [unreadMessages enumerateKeysAndObjectsInGroup:self.uniqueId - usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { - if (![object conformsToProtocol:@protocol(OWSReadTracking)]) { - return; - } - id unread = (id)object; - if (unread.read) { - NSLog(@"Found an already read message in the * unread * messages list."); - return; - } - count += 1; - }]; - + count = [unreadMessages numberOfItemsInGroup:self.uniqueId]; return count; -} - -- (BOOL)hasUnreadMentionMessageWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - __block BOOL hasUnreadMention = false; - - YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName]; - [unreadMessages enumerateKeysAndObjectsInGroup:self.uniqueId - usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { - if (![object isKindOfClass:[TSIncomingMessage class]]) { - return; - } - TSIncomingMessage* unreadMessage = (TSIncomingMessage*)object; - if (unreadMessage.read) { - NSLog(@"Found an already read message in the * unread * messages list."); - return; - } - - if (unreadMessage.isUserMentioned) { - hasUnreadMention = true; - *stop = YES; - } - }]; - - return hasUnreadMention; + // FIXME: Why did we have to do as the following? +// [unreadMessages enumerateKeysAndObjectsInGroup:self.uniqueId +// usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { +// if (![object conformsToProtocol:@protocol(OWSReadTracking)]) { +// return; +// } +// id unread = (id)object; +// if (unread.read) { +// NSLog(@"Found an already read message in the * unread * messages list."); +// return; +// } +// count += 1; +// }]; } - (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction @@ -301,6 +277,10 @@ BOOL IsNoteToSelfEnabled(void) for (id message in [self unseenMessagesWithTransaction:transaction]) { [message markAsReadAtTimestamp:[NSDate ows_millisecondTimeStamp] sendReadReceipt:YES transaction:transaction]; } + + // Update unread mention. + self.hasUnreadMentionMessage = false; + [super saveWithTransaction:transaction]; } - (nullable TSInteraction *)lastInteractionForInboxWithTransaction:(YapDatabaseReadTransaction *)transaction @@ -366,6 +346,12 @@ BOOL IsNoteToSelfEnabled(void) _lastInteractionDate = lastMessage.receivedAtDate; [super saveWithTransaction:transaction]; } + + // Update unread mention if there is a new incoming message. + if ([lastMessage isKindOfClass:[TSIncomingMessage class]] && ((TSIncomingMessage *)lastMessage).isUserMentioned) { + self.hasUnreadMentionMessage = true; + [super saveWithTransaction:transaction]; + } if (!self.shouldBeVisible) { self.shouldBeVisible = YES; diff --git a/SignalUtilitiesKit/Messaging/ThreadViewModel.swift b/SignalUtilitiesKit/Messaging/ThreadViewModel.swift index daf54062c..d68bf7d75 100644 --- a/SignalUtilitiesKit/Messaging/ThreadViewModel.swift +++ b/SignalUtilitiesKit/Messaging/ThreadViewModel.swift @@ -52,7 +52,7 @@ public class ThreadViewModel: NSObject { self.unreadCount = thread.unreadMessageCount(transaction: transaction) self.hasUnreadMessages = unreadCount > 0 - self.hasUnreadMentions = thread.hasUnreadMentionMessage(transaction: transaction) + self.hasUnreadMentions = thread.hasUnreadMentionMessage } @objc From 6d99976a9cfc1f6af530f78ebe68fd1e377affbf Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 21 Feb 2022 15:28:51 +1100 Subject: [PATCH 13/46] database migrate for unread mention --- Session.xcodeproj/project.pbxproj | 4 ++ .../Migrations/OWSDatabaseMigrationRunner.m | 3 +- .../Migrations/UnreadMentionMigtation.swift | 41 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 SignalUtilitiesKit/Database/Migrations/UnreadMentionMigtation.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index b934a41cc..37db20b47 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -136,6 +136,7 @@ 7B1581E2271E743B00848B49 /* OWSSounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E1271E743B00848B49 /* OWSSounds.swift */; }; 7B1D74AA27BCC16E0030B423 /* NSENotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74A927BCC16E0030B423 /* NSENotificationPresenter.swift */; }; 7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */; }; + 7B1D74AE27C346220030B423 /* UnreadMentionMigtation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AD27C346220030B423 /* UnreadMentionMigtation.swift */; }; 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; }; 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; }; 7B7CB18B270591630079FF93 /* ShareLogsModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18A270591630079FF93 /* ShareLogsModal.swift */; }; @@ -1117,6 +1118,7 @@ 7B1581E1271E743B00848B49 /* OWSSounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSSounds.swift; sourceTree = ""; }; 7B1D74A927BCC16E0030B423 /* NSENotificationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSENotificationPresenter.swift; sourceTree = ""; }; 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+Timeout.swift"; sourceTree = ""; }; + 7B1D74AD27C346220030B423 /* UnreadMentionMigtation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadMentionMigtation.swift; sourceTree = ""; }; 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = ""; }; 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = ""; }; @@ -3044,6 +3046,7 @@ isa = PBXGroup; children = ( B8B32044258C117C0020074B /* ContactsMigration.swift */, + 7B1D74AD27C346220030B423 /* UnreadMentionMigtation.swift */, C38EF271255B6D79007E1867 /* OWSDatabaseMigration.h */, C38EF270255B6D79007E1867 /* OWSDatabaseMigration.m */, C38EF26F255B6D79007E1867 /* OWSDatabaseMigrationRunner.h */, @@ -4571,6 +4574,7 @@ C38EF31D255B6DBF007E1867 /* UIImage+OWS.swift in Sources */, C3AAFFE825AE975D0089E6DD /* ConfigurationMessage+Convenience.swift in Sources */, C38EF359255B6DCC007E1867 /* SheetViewController.swift in Sources */, + 7B1D74AE27C346220030B423 /* UnreadMentionMigtation.swift in Sources */, B8F5F52925EC4F8A003BF8D4 /* BlockListUIUtils.m in Sources */, C38EF386255B6DD2007E1867 /* AttachmentApprovalInputAccessoryView.swift in Sources */, B8C2B2C82563685C00551B4D /* CircleView.swift in Sources */, diff --git a/SignalUtilitiesKit/Database/Migrations/OWSDatabaseMigrationRunner.m b/SignalUtilitiesKit/Database/Migrations/OWSDatabaseMigrationRunner.m index fd8ea2d2c..3bc52cc64 100644 --- a/SignalUtilitiesKit/Database/Migrations/OWSDatabaseMigrationRunner.m +++ b/SignalUtilitiesKit/Database/Migrations/OWSDatabaseMigrationRunner.m @@ -26,7 +26,8 @@ NS_ASSUME_NONNULL_BEGIN - (NSArray *)allMigrations { return @[ - [SNContactsMigration new] + [SNContactsMigration new], + [SNUnreadMentionMigration new] ]; } diff --git a/SignalUtilitiesKit/Database/Migrations/UnreadMentionMigtation.swift b/SignalUtilitiesKit/Database/Migrations/UnreadMentionMigtation.swift new file mode 100644 index 000000000..c23c327fb --- /dev/null +++ b/SignalUtilitiesKit/Database/Migrations/UnreadMentionMigtation.swift @@ -0,0 +1,41 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +@objc(SNUnreadMentionMigration) +public class UnreadMentionMigration : OWSDatabaseMigration { + + @objc + class func migrationId() -> String { + return "003" // leave "002" for message request migration + } + + override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) { + self.doMigrationAsync(completion: completion) + } + + private func doMigrationAsync(completion: @escaping OWSDatabaseMigrationCompletion) { + var threads: [TSThread] = [] + Storage.read { transaction in + TSThread.enumerateCollectionObjects(with: transaction) { object, _ in + guard let thread = object as? TSThread, let threadID = thread.uniqueId else { return } + let unreadMessages = transaction.ext(TSUnreadDatabaseViewExtensionName) as! YapDatabaseViewTransaction + unreadMessages.enumerateKeysAndObjects(inGroup: threadID) { collection, key, object, index, stop in + guard let unreadMessage = object as? TSIncomingMessage else { return } + if unreadMessage.wasRead { return } + if unreadMessage.isUserMentioned { + thread.hasUnreadMentionMessage = true + stop.pointee = true + } + } + threads.append(thread) + } + } + Storage.write(with: { transaction in + threads.forEach { thread in + thread.save(with: transaction) + } + self.save(with: transaction) // Intentionally capture self + }, completion: { + completion() + }) + } +} From cb127be5a2003386e3fa83cbff4c1cbff3393880 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 21 Feb 2022 15:44:52 +1100 Subject: [PATCH 14/46] minor clean --- SessionMessagingKit/Threads/TSThread.m | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/SessionMessagingKit/Threads/TSThread.m b/SessionMessagingKit/Threads/TSThread.m index ecc9c7a04..feb3a6539 100644 --- a/SessionMessagingKit/Threads/TSThread.m +++ b/SessionMessagingKit/Threads/TSThread.m @@ -251,13 +251,14 @@ BOOL IsNoteToSelfEnabled(void) - (NSUInteger)unreadMessageCountWithTransaction:(YapDatabaseReadTransaction *)transaction { - __block NSUInteger count = 0; - YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName]; - count = [unreadMessages numberOfItemsInGroup:self.uniqueId]; - return count; + return [unreadMessages numberOfItemsInGroup:self.uniqueId]; + // FIXME: Why did we have to do as the following? +// __block NSUInteger count = 0; +// +// YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName]; // [unreadMessages enumerateKeysAndObjectsInGroup:self.uniqueId // usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { // if (![object conformsToProtocol:@protocol(OWSReadTracking)]) { @@ -270,6 +271,8 @@ BOOL IsNoteToSelfEnabled(void) // } // count += 1; // }]; + +// return count; } - (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction From e428333b7b9c50c881e7016943e7671fc83f0876 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 21 Feb 2022 16:58:42 +1100 Subject: [PATCH 15/46] minor update to reduce database read on ui thread --- Session/Conversations/ConversationVC.swift | 1 + .../Conversations/Message Cells/MessageCell.swift | 12 +++++++++++- .../Message Cells/VisibleMessageCell.swift | 10 +++++----- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 34486c858..990d2de75 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -291,6 +291,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat let viewItem = viewItems[indexPath.row] let cell = tableView.dequeueReusableCell(withIdentifier: MessageCell.getCellType(for: viewItem).identifier) as! MessageCell cell.delegate = self + cell.thread = thread cell.viewItem = viewItem return cell } diff --git a/Session/Conversations/Message Cells/MessageCell.swift b/Session/Conversations/Message Cells/MessageCell.swift index a2d846692..889e3c19d 100644 --- a/Session/Conversations/Message Cells/MessageCell.swift +++ b/Session/Conversations/Message Cells/MessageCell.swift @@ -1,4 +1,5 @@ import UIKit +import SessionMessagingKit public enum SwipeState { case began @@ -8,7 +9,16 @@ public enum SwipeState { class MessageCell : UITableViewCell { weak var delegate: MessageCellDelegate? - var viewItem: ConversationViewItem? { didSet { update() } } + var thread: TSThread? { + didSet { + if viewItem != nil { update() } + } + } + var viewItem: ConversationViewItem? { + didSet { + if thread != nil { update() } + } + } // MARK: Settings class var identifier: String { preconditionFailure("Must be overridden by subclasses.") } diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 1106b2d10..df8f10244 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -257,7 +257,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate { messageStatusImageView.image = image messageStatusImageView.backgroundColor = backgroundColor if let message = message as? TSOutgoingMessage { - messageStatusImageView.isHidden = (message.messageState == .sent && message.thread.lastInteraction != message) + messageStatusImageView.isHidden = (message.messageState == .sent && thread?.lastInteraction != message) } else { messageStatusImageView.isHidden = true } @@ -349,7 +349,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate { } case .mediaMessage: if viewItem.interaction is TSIncomingMessage, - let thread = viewItem.interaction.thread as? TSContactThread, + let thread = thread as? TSContactThread, Storage.shared.getContact(with: thread.contactSessionID())?.isTrusted != true { showMediaPlaceholder() } else { @@ -382,7 +382,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate { } case .audio: if viewItem.interaction is TSIncomingMessage, - let thread = viewItem.interaction.thread as? TSContactThread, + let thread = thread as? TSContactThread, Storage.shared.getContact(with: thread.contactSessionID())?.isTrusted != true { showMediaPlaceholder() } else { @@ -393,7 +393,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate { } case .genericAttachment: if viewItem.interaction is TSIncomingMessage, - let thread = viewItem.interaction.thread as? TSContactThread, + let thread = thread as? TSContactThread, Storage.shared.getContact(with: thread.contactSessionID())?.isTrusted != true { showMediaPlaceholder() } else { @@ -673,7 +673,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate { private static func shouldShowProfilePicture(for viewItem: ConversationViewItem) -> Bool { guard let message = viewItem.interaction as? TSMessage else { preconditionFailure() } - let isGroupThread = message.thread.isGroupThread() + let isGroupThread = viewItem.isGroupThread let senderSessionID = (message as? TSIncomingMessage)?.authorId return isGroupThread && viewItem.shouldShowSenderProfilePicture && senderSessionID != nil } From d21c5329e3aad1befef2f2efcccfc9fd276b774e Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 21 Feb 2022 17:01:33 +1100 Subject: [PATCH 16/46] further improvements to move poller into background threads --- SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index e0bbe578f..cecc33f9b 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -89,7 +89,7 @@ public final class Poller : NSObject { private func poll(_ snode: Snode, seal longTermSeal: Resolver) -> Promise { guard isPolling else { return Promise { $0.fulfill(()) } } let userPublicKey = getUserHexEncodedPublicKey() - return SnodeAPI.getRawMessages(from: snode, associatedWith: userPublicKey).then(on: DispatchQueue.main) { [weak self] rawResponse -> Promise in + return SnodeAPI.getRawMessages(from: snode, associatedWith: userPublicKey).then(on: Threading.pollerQueue) { [weak self] rawResponse -> Promise in guard let strongSelf = self, strongSelf.isPolling else { return Promise { $0.fulfill(()) } } let messages = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: userPublicKey) if !messages.isEmpty { @@ -111,7 +111,7 @@ public final class Poller : NSObject { if strongSelf.pollCount == Poller.maxPollCount { throw Error.pollLimitReached } else { - return withDelay(Poller.pollInterval, completionQueue: DispatchQueue.main) { + return withDelay(Poller.pollInterval, completionQueue: Threading.pollerQueue) { guard let strongSelf = self, strongSelf.isPolling else { return Promise { $0.fulfill(()) } } return strongSelf.poll(snode, seal: longTermSeal) } From eec3d31109171636784d6472c7d5894ca5f498a0 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 21 Feb 2022 17:22:58 +1100 Subject: [PATCH 17/46] WIP: timer + main thread --- Session.xcodeproj/project.pbxproj | 4 ++++ SessionUtilitiesKit/General/Timer+MainThread.swift | 13 +++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 SessionUtilitiesKit/General/Timer+MainThread.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 37db20b47..7f449ec9d 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -137,6 +137,7 @@ 7B1D74AA27BCC16E0030B423 /* NSENotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74A927BCC16E0030B423 /* NSENotificationPresenter.swift */; }; 7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */; }; 7B1D74AE27C346220030B423 /* UnreadMentionMigtation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AD27C346220030B423 /* UnreadMentionMigtation.swift */; }; + 7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */; }; 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; }; 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; }; 7B7CB18B270591630079FF93 /* ShareLogsModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18A270591630079FF93 /* ShareLogsModal.swift */; }; @@ -1119,6 +1120,7 @@ 7B1D74A927BCC16E0030B423 /* NSENotificationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSENotificationPresenter.swift; sourceTree = ""; }; 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+Timeout.swift"; sourceTree = ""; }; 7B1D74AD27C346220030B423 /* UnreadMentionMigtation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadMentionMigtation.swift; sourceTree = ""; }; + 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timer+MainThread.swift"; sourceTree = ""; }; 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = ""; }; 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = ""; }; @@ -2348,6 +2350,7 @@ C33FDB45255A580C00E217F9 /* NSString+SSK.m */, C352A3762557859C00338F3E /* NSTimer+Proxying.h */, C352A36C2557858D00338F3E /* NSTimer+Proxying.m */, + 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */, C33FDB51255A580D00E217F9 /* NSUserDefaults+OWS.h */, C33FDB77255A581000E217F9 /* NSUserDefaults+OWS.m */, C33FDB14255A580800E217F9 /* OWSMath.h */, @@ -4619,6 +4622,7 @@ 7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */, C32C5A47256DB8F0003C73A2 /* ECKeyPair+Hexadecimal.swift in Sources */, C3D9E41525676C320040E4F3 /* Storage.swift in Sources */, + 7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */, C32C5D83256DD5B6003C73A2 /* SSKKeychainStorage.swift in Sources */, C3D9E39B256763C20040E4F3 /* AppContext.m in Sources */, C3BBE0A82554D4DE0050F1E3 /* JSON.swift in Sources */, diff --git a/SessionUtilitiesKit/General/Timer+MainThread.swift b/SessionUtilitiesKit/General/Timer+MainThread.swift new file mode 100644 index 000000000..b8a5ce314 --- /dev/null +++ b/SessionUtilitiesKit/General/Timer+MainThread.swift @@ -0,0 +1,13 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension Timer { + + @discardableResult + public static func scheduledTimerOnMainThread(withTimeInterval timeInterval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer { + let timer = Timer(timeInterval: timeInterval, repeats: repeats, block: block) + RunLoop.main.add(timer, forMode: .common) + return timer + } +} From b2ab984586a4ca5e2d6f0def86343bf4cafbc91d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 22 Feb 2022 14:01:48 +1100 Subject: [PATCH 18/46] refactor on timer and polling threading --- .../Pollers/ClosedGroupPoller.swift | 24 +++++-------- .../Pollers/OpenGroupPollerV2.swift | 35 ++++++++----------- .../Sending & Receiving/Pollers/Poller.swift | 27 +++++++------- .../PromiseKit/Promise+Delaying.swift | 11 +++--- 4 files changed, 44 insertions(+), 53 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index 459b6c56b..a65220a3a 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -61,13 +61,11 @@ public final class ClosedGroupPoller : NSObject { // MARK: Private API private func setUpPolling(for groupPublicKey: String) { - poll(groupPublicKey).done2 { [weak self] _ in - DispatchQueue.main.async { // Timers don't do well on background queues + Threading.closedGroupPollerQueue.async { + self.poll(groupPublicKey).done(on: Threading.closedGroupPollerQueue) { [weak self] _ in self?.pollRecursively(groupPublicKey) - } - }.catch2 { [weak self] error in - // The error is logged in poll(_:) - DispatchQueue.main.async { // Timers don't do well on background queues + }.catch(on: Threading.closedGroupPollerQueue) { [weak self] error in + // The error is logged in poll(_:) self?.pollRecursively(groupPublicKey) } } @@ -87,18 +85,14 @@ public final class ClosedGroupPoller : NSObject { let a = (ClosedGroupPoller.maxPollInterval - minPollInterval) / limit let nextPollInterval = a * min(timeSinceLastMessage, limit) + minPollInterval SNLog("Next poll interval for closed group with public key: \(groupPublicKey) is \(nextPollInterval) s.") - timers[groupPublicKey] = Timer.scheduledTimer(withTimeInterval: nextPollInterval, repeats: false) { [weak self] timer in + timers[groupPublicKey] = Timer.scheduledTimerOnMainThread(withTimeInterval: nextPollInterval, repeats: false) { [weak self] timer in timer.invalidate() Threading.closedGroupPollerQueue.async { - self?.poll(groupPublicKey).done2 { _ in - DispatchQueue.main.async { // Timers don't do well on background queues - self?.pollRecursively(groupPublicKey) - } - }.catch2 { error in + self?.poll(groupPublicKey).done(on: Threading.closedGroupPollerQueue) { _ in + self?.pollRecursively(groupPublicKey) + }.catch(on: Threading.closedGroupPollerQueue) { error in // The error is logged in poll(_:) - DispatchQueue.main.async { // Timers don't do well on background queues - self?.pollRecursively(groupPublicKey) - } + self?.pollRecursively(groupPublicKey) } } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift index 9e50c2717..8ef1ffcca 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift @@ -19,18 +19,11 @@ public final class OpenGroupPollerV2 : NSObject { @objc public func startIfNeeded() { guard !hasStarted else { return } - DispatchQueue.main.async { [weak self] in // Timers don't do well on background queues - guard let strongSelf = self else { return } - strongSelf.hasStarted = true - strongSelf.timer = Timer.scheduledTimer(withTimeInterval: strongSelf.pollInterval, repeats: true) { _ in - Threading.openGroupPollerQueue.async { - self?.poll().retainUntilComplete() - } - } - Threading.openGroupPollerQueue.async { - strongSelf.poll().retainUntilComplete() - } + hasStarted = true + timer = Timer.scheduledTimerOnMainThread(withTimeInterval: pollInterval, repeats: true) { _ in + self.poll().retainUntilComplete() } + poll().retainUntilComplete() } @objc public func stop() { @@ -50,15 +43,17 @@ public final class OpenGroupPollerV2 : NSObject { self.isPolling = true let (promise, seal) = Promise.pending() promise.retainUntilComplete() - OpenGroupAPIV2.compactPoll(server).done(on: OpenGroupAPIV2.workQueue) { [weak self] bodies in - guard let self = self else { return } - self.isPolling = false - bodies.forEach { self.handleCompactPollBody($0, isBackgroundPoll: isBackgroundPoll) } - seal.fulfill(()) - }.catch(on: OpenGroupAPIV2.workQueue) { error in - SNLog("Open group polling failed due to error: \(error).") - self.isPolling = false - seal.fulfill(()) // The promise is just used to keep track of when we're done + Threading.openGroupPollerQueue.async { + OpenGroupAPIV2.compactPoll(self.server).done(on: OpenGroupAPIV2.workQueue) { [weak self] bodies in + guard let self = self else { return } + self.isPolling = false + bodies.forEach { self.handleCompactPollBody($0, isBackgroundPoll: isBackgroundPoll) } + seal.fulfill(()) + }.catch(on: OpenGroupAPIV2.workQueue) { error in + SNLog("Open group polling failed due to error: \(error).") + self.isPolling = false + seal.fulfill(()) // The promise is just used to keep track of when we're done + } } return promise } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index cecc33f9b..8885e5542 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -45,21 +45,22 @@ public final class Poller : NSObject { // MARK: Private API private func setUpPolling() { guard isPolling else { return } - let _ = SnodeAPI.getSwarm(for: getUserHexEncodedPublicKey()).then2 { [weak self] _ -> Promise in - guard let strongSelf = self else { return Promise { $0.fulfill(()) } } - strongSelf.usedSnodes.removeAll() - let (promise, seal) = Promise.pending() - strongSelf.pollNextSnode(seal: seal) - return promise - }.ensure(on: DispatchQueue.main) { [weak self] in // Timers don't do well on background queues - guard let strongSelf = self, strongSelf.isPolling else { return } - Timer.scheduledTimer(withTimeInterval: Poller.retryInterval, repeats: false) { _ in - guard let strongSelf = self else { return } - Threading.pollerQueue.async { + Threading.pollerQueue.async { + let _ = SnodeAPI.getSwarm(for: getUserHexEncodedPublicKey()).then(on: Threading.pollerQueue) { [weak self] _ -> Promise in + guard let strongSelf = self else { return Promise { $0.fulfill(()) } } + strongSelf.usedSnodes.removeAll() + let (promise, seal) = Promise.pending() + strongSelf.pollNextSnode(seal: seal) + return promise + }.ensure(on: Threading.pollerQueue) { [weak self] in // Timers don't do well on background queues + guard let strongSelf = self, strongSelf.isPolling else { return } + Timer.scheduledTimerOnMainThread(withTimeInterval: Poller.retryInterval, repeats: false) { _ in + guard let strongSelf = self else { return } strongSelf.setUpPolling() } } } + } private func pollNextSnode(seal: Resolver) { @@ -70,9 +71,9 @@ public final class Poller : NSObject { // randomElement() uses the system's default random generator, which is cryptographically secure let nextSnode = unusedSnodes.randomElement()! usedSnodes.insert(nextSnode) - poll(nextSnode, seal: seal).done2 { + poll(nextSnode, seal: seal).done(on: Threading.pollerQueue) { seal.fulfill(()) - }.catch2 { [weak self] error in + }.catch(on: Threading.pollerQueue) { [weak self] error in if let error = error as? Error, error == .pollLimitReached { self?.pollCount = 0 } else { diff --git a/SessionUtilitiesKit/PromiseKit/Promise+Delaying.swift b/SessionUtilitiesKit/PromiseKit/Promise+Delaying.swift index 7e02f4e88..02401a14e 100644 --- a/SessionUtilitiesKit/PromiseKit/Promise+Delaying.swift +++ b/SessionUtilitiesKit/PromiseKit/Promise+Delaying.swift @@ -2,12 +2,13 @@ import PromiseKit /// Delay the execution of the promise constructed in `body` by `delay` seconds. public func withDelay(_ delay: TimeInterval, completionQueue: DispatchQueue, body: @escaping () -> Promise) -> Promise { - #if DEBUG - assert(Thread.current.isMainThread) // Timers don't do well on background queues - #endif let (promise, seal) = Promise.pending() - Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { _ in - body().done(on: completionQueue) { seal.fulfill($0) }.catch(on: completionQueue) { seal.reject($0) } + Timer.scheduledTimerOnMainThread(withTimeInterval: delay, repeats: false) { _ in + body().done(on: completionQueue) { + seal.fulfill($0) + }.catch(on: completionQueue) { + seal.reject($0) + } } return promise } From 5954c109a1ca290cc7302aac5dca262daf8daa2e Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 23 Feb 2022 10:09:14 +1100 Subject: [PATCH 19/46] Fix an issue where message would be sent even though the attachments are failed to upload --- .../MessageSender+Convenience.swift | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift b/SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift index 3297ce14e..af4f0d7e5 100644 --- a/SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift +++ b/SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift @@ -91,12 +91,15 @@ extension MessageSender { let errors = results.compactMap { result -> Swift.Error? in if case .rejected(let error) = result { return error } else { return nil } } - if let error = errors.first { seal.reject(error) } - Storage.write{ transaction in - sendNonDurably(message, in: thread, using: transaction).done { - seal.fulfill(()) - }.catch { error in - seal.reject(error) + if let error = errors.first { + seal.reject(error) + } else { + Storage.write{ transaction in + sendNonDurably(message, in: thread, using: transaction).done { + seal.fulfill(()) + }.catch { error in + seal.reject(error) + } } } return promise From cd97b9c3e891e861c7d11fb1733274ff433f759d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 23 Feb 2022 12:47:44 +1100 Subject: [PATCH 20/46] minor fix on poller threading --- .../Sending & Receiving/Pollers/Poller.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 8885e5542..623acf1ff 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -71,16 +71,18 @@ public final class Poller : NSObject { // randomElement() uses the system's default random generator, which is cryptographically secure let nextSnode = unusedSnodes.randomElement()! usedSnodes.insert(nextSnode) - poll(nextSnode, seal: seal).done(on: Threading.pollerQueue) { + poll(nextSnode, seal: seal).done2 { seal.fulfill(()) - }.catch(on: Threading.pollerQueue) { [weak self] error in + }.catch2 { [weak self] error in if let error = error as? Error, error == .pollLimitReached { self?.pollCount = 0 } else { SNLog("Polling \(nextSnode) failed; dropping it and switching to next snode.") SnodeAPI.dropSnodeFromSwarmIfNeeded(nextSnode, publicKey: userPublicKey) } - self?.pollNextSnode(seal: seal) + Threading.pollerQueue.async { + self?.pollNextSnode(seal: seal) + } } } else { seal.fulfill(()) From 7c2e25e25a5a2d02230b52772a874c06fe409206 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 23 Feb 2022 15:31:06 +1100 Subject: [PATCH 21/46] remove useless code --- .../Utilities/FullTextSearchFinder.swift | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/SessionMessagingKit/Utilities/FullTextSearchFinder.swift b/SessionMessagingKit/Utilities/FullTextSearchFinder.swift index 78ea59d5d..0320aeaf2 100644 --- a/SessionMessagingKit/Utilities/FullTextSearchFinder.swift +++ b/SessionMessagingKit/Utilities/FullTextSearchFinder.swift @@ -30,8 +30,6 @@ public class FullTextSearchFinder: NSObject { private static var tsAccountManager: TSAccountManager { return TSAccountManager.sharedInstance() } - - public var newQueryTimestamp: UInt64 = 0 // MARK: - Querying @@ -91,13 +89,6 @@ public class FullTextSearchFinder: NSObject { guard let ext: YapDatabaseFullTextSearchTransaction = ext(transaction: transaction) else { return } - - // HACK: Full text search is too expensive even though we drop the max search results - // to a reasonable number. And the async read can sometimes block a thread and make - // other read threads wait for it to finish. The timestamp is a workaround to ensure - // only one thread can be running the full text search at one time. - let currentQueryTimestamp = NSDate.millisecondTimestamp() - newQueryTimestamp = currentQueryTimestamp let query = FullTextSearchFinder.query(searchText: searchText) @@ -108,7 +99,7 @@ public class FullTextSearchFinder: NSObject { snippetOptions.endMatchText = "" snippetOptions.numberOfTokens = 5 ext.enumerateKeysAndObjects(matching: query, with: snippetOptions) { (snippet: String, _: String, _: String, object: Any, stop: UnsafeMutablePointer) in - guard searchResultCount < maxSearchResults && currentQueryTimestamp >= self.newQueryTimestamp else { + guard searchResultCount < maxSearchResults else { stop.pointee = true return } From d27faf551bf627834749b7e1b220ed6942fbb40d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 23 Feb 2022 15:49:19 +1100 Subject: [PATCH 22/46] reduce unnecessary database read --- .../GlobalSearch/GlobalSearchViewController.swift | 14 ++++++-------- Session/Shared/ConversationCell.swift | 5 +++-- .../Messaging/FullTextSearcher.swift | 13 +++++-------- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/Session/Home/GlobalSearch/GlobalSearchViewController.swift b/Session/Home/GlobalSearch/GlobalSearchViewController.swift index 4d1691364..8bb5829c0 100644 --- a/Session/Home/GlobalSearch/GlobalSearchViewController.swift +++ b/Session/Home/GlobalSearch/GlobalSearchViewController.swift @@ -192,12 +192,12 @@ extension GlobalSearchViewController { SNLog("shouldn't be able to tap 'no results' section") case .contacts: let sectionResults = searchResultSet.conversations - guard let searchResult = sectionResults[safe: indexPath.row], let threadId = searchResult.thread.threadRecord.uniqueId, let thread = TSThread.fetch(uniqueId: threadId) else { return } - show(thread, highlightedMessageID: nil, animated: true) + guard let searchResult = sectionResults[safe: indexPath.row] else { return } + show(searchResult.thread.threadRecord, highlightedMessageID: nil, animated: true) case .messages: let sectionResults = searchResultSet.messages - guard let searchResult = sectionResults[safe: indexPath.row], let threadId = searchResult.thread.threadRecord.uniqueId, let thread = TSThread.fetch(uniqueId: threadId) else { return } - show(thread, highlightedMessageID: searchResult.messageId, animated: true) + guard let searchResult = sectionResults[safe: indexPath.row] else { return } + show(searchResult.thread.threadRecord, highlightedMessageID: searchResult.message?.uniqueId, animated: true) case .recent: guard let threadId = recentSearchResults[safe: indexPath.row], let thread = TSThread.fetch(uniqueId: threadId) else { return } show(thread, highlightedMessageID: nil, animated: true, isFromRecent: true) @@ -336,7 +336,7 @@ extension GlobalSearchViewController { cell.isShowingGlobalSearchResult = true let searchResult = sectionResults[safe: indexPath.row] cell.threadViewModel = searchResult?.thread - cell.configure(messageDate: searchResult?.messageDate, snippet: searchResult?.snippet, searchText: searchResultSet.searchText) + cell.configure(snippet: searchResult?.snippet, searchText: searchResultSet.searchText) return cell case .messages: let sectionResults = searchResultSet.messages @@ -344,9 +344,7 @@ extension GlobalSearchViewController { cell.isShowingGlobalSearchResult = true let searchResult = sectionResults[safe: indexPath.row] cell.threadViewModel = searchResult?.thread - var message: TSMessage? = nil - if let messageId = searchResult?.messageId { message = TSMessage.fetch(uniqueId: messageId) } - cell.configure(messageDate: searchResult?.messageDate, snippet: searchResult?.snippet, searchText: searchResultSet.searchText, message: message) + cell.configure(snippet: searchResult?.snippet, searchText: searchResultSet.searchText, message: searchResult?.message) return cell case .recent: let cell = tableView.dequeueReusableCell(withIdentifier: ConversationCell.reuseIdentifier) as! ConversationCell diff --git a/Session/Shared/ConversationCell.swift b/Session/Shared/ConversationCell.swift index 2f6a47f2f..8369778c8 100644 --- a/Session/Shared/ConversationCell.swift +++ b/Session/Shared/ConversationCell.swift @@ -219,10 +219,11 @@ final class ConversationCell : UITableViewCell { timestampLabel.isHidden = true } - public func configure(messageDate: Date?, snippet: String?, searchText: String, message: TSMessage? = nil) { + public func configure(snippet: String?, searchText: String, message: TSMessage? = nil) { let normalizedSearchText = searchText.lowercased() - if let messageDate = messageDate, let snippet = snippet { + if let messageTimestamp = message?.timestamp, let snippet = snippet { // Message + let messageDate = NSDate.ows_date(withMillisecondsSince1970: messageTimestamp) displayNameLabel.attributedText = NSMutableAttributedString(string: getDisplayName(), attributes: [.foregroundColor:Colors.text]) timestampLabel.isHidden = false timestampLabel.text = DateUtil.formatDate(forDisplay: messageDate) diff --git a/SignalUtilitiesKit/Messaging/FullTextSearcher.swift b/SignalUtilitiesKit/Messaging/FullTextSearcher.swift index 236ad7e44..8efe00335 100644 --- a/SignalUtilitiesKit/Messaging/FullTextSearcher.swift +++ b/SignalUtilitiesKit/Messaging/FullTextSearcher.swift @@ -22,18 +22,16 @@ public struct ConversationSortKey: Comparable { public class ConversationSearchResult: Comparable where SortKey: Comparable { public let thread: ThreadViewModel - public let messageId: String? - public let messageDate: Date? + public let message: TSMessage? public let snippet: String? private let sortKey: SortKey - init(thread: ThreadViewModel, sortKey: SortKey, messageId: String? = nil, messageDate: Date? = nil, snippet: String? = nil) { + init(thread: ThreadViewModel, sortKey: SortKey, message: TSMessage? = nil, snippet: String? = nil) { self.thread = thread self.sortKey = sortKey - self.messageId = messageId - self.messageDate = messageDate + self.message = message self.snippet = snippet } @@ -47,7 +45,7 @@ public class ConversationSearchResult: Comparable where SortKey: Compar public static func == (lhs: ConversationSearchResult, rhs: ConversationSearchResult) -> Bool { return lhs.thread.threadRecord.uniqueId == rhs.thread.threadRecord.uniqueId && - lhs.messageId == rhs.messageId + lhs.message?.uniqueId == rhs.message?.uniqueId } } @@ -270,8 +268,7 @@ public class FullTextSearcher: NSObject { let sortKey = message.sortId let searchResult = ConversationSearchResult(thread: threadViewModel, sortKey: sortKey, - messageId: message.uniqueId, - messageDate: NSDate.ows_date(withMillisecondsSince1970: message.timestamp), + message: message, snippet: snippet) messages.append(searchResult) From d61f36211d1abf99f874718d259a870a53be820c Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 24 Feb 2022 09:43:21 +1100 Subject: [PATCH 23/46] bump up to show 500 global search results for all cases --- Session/Home/GlobalSearch/GlobalSearchViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Session/Home/GlobalSearch/GlobalSearchViewController.swift b/Session/Home/GlobalSearch/GlobalSearchViewController.swift index 8bb5829c0..9d1f35dba 100644 --- a/Session/Home/GlobalSearch/GlobalSearchViewController.swift +++ b/Session/Home/GlobalSearch/GlobalSearchViewController.swift @@ -133,7 +133,7 @@ class GlobalSearchViewController: BaseVC, UITableViewDelegate, UITableViewDataSo self.isLoading = true // The max search result count is set according to the keyword length. This is just a workaround for performance issue. // The longer and more accurate the keyword is, the less search results should there be. - searchResults = self.searcher.searchForHomeScreen(searchText: searchText, maxSearchResults: min(searchText.count * 50, 500), transaction: transaction) + searchResults = self.searcher.searchForHomeScreen(searchText: searchText, maxSearchResults: 500, transaction: transaction) }, completionBlock: { [weak self] in AssertIsOnMainThread() guard let self = self, let results = searchResults, self.lastSearchText == searchText else { return } From 7a677a180072e4a64698f2b1111832264d996e51 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 24 Feb 2022 16:47:08 +1100 Subject: [PATCH 24/46] move pollers to one working queue to avoid race condition --- .../Pollers/ClosedGroupPoller.swift | 12 ++++++------ .../Pollers/OpenGroupPollerV2.swift | 2 +- SessionMessagingKit/Utilities/Threading.swift | 2 -- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index a65220a3a..eb5966cd5 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -61,10 +61,10 @@ public final class ClosedGroupPoller : NSObject { // MARK: Private API private func setUpPolling(for groupPublicKey: String) { - Threading.closedGroupPollerQueue.async { - self.poll(groupPublicKey).done(on: Threading.closedGroupPollerQueue) { [weak self] _ in + Threading.pollerQueue.async { + self.poll(groupPublicKey).done(on: Threading.pollerQueue) { [weak self] _ in self?.pollRecursively(groupPublicKey) - }.catch(on: Threading.closedGroupPollerQueue) { [weak self] error in + }.catch(on: Threading.pollerQueue) { [weak self] error in // The error is logged in poll(_:) self?.pollRecursively(groupPublicKey) } @@ -87,10 +87,10 @@ public final class ClosedGroupPoller : NSObject { SNLog("Next poll interval for closed group with public key: \(groupPublicKey) is \(nextPollInterval) s.") timers[groupPublicKey] = Timer.scheduledTimerOnMainThread(withTimeInterval: nextPollInterval, repeats: false) { [weak self] timer in timer.invalidate() - Threading.closedGroupPollerQueue.async { - self?.poll(groupPublicKey).done(on: Threading.closedGroupPollerQueue) { _ in + Threading.pollerQueue.async { + self?.poll(groupPublicKey).done(on: Threading.pollerQueue) { _ in self?.pollRecursively(groupPublicKey) - }.catch(on: Threading.closedGroupPollerQueue) { error in + }.catch(on: Threading.pollerQueue) { error in // The error is logged in poll(_:) self?.pollRecursively(groupPublicKey) } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift index 8ef1ffcca..4c2ddbc8a 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift @@ -43,7 +43,7 @@ public final class OpenGroupPollerV2 : NSObject { self.isPolling = true let (promise, seal) = Promise.pending() promise.retainUntilComplete() - Threading.openGroupPollerQueue.async { + Threading.pollerQueue.async { OpenGroupAPIV2.compactPoll(self.server).done(on: OpenGroupAPIV2.workQueue) { [weak self] bodies in guard let self = self else { return } self.isPolling = false diff --git a/SessionMessagingKit/Utilities/Threading.swift b/SessionMessagingKit/Utilities/Threading.swift index 83f34ffd6..10db310b6 100644 --- a/SessionMessagingKit/Utilities/Threading.swift +++ b/SessionMessagingKit/Utilities/Threading.swift @@ -5,6 +5,4 @@ internal enum Threading { internal static let jobQueue = DispatchQueue(label: "SessionMessagingKit.jobQueue", qos: .userInitiated) internal static let pollerQueue = DispatchQueue(label: "SessionMessagingKit.pollerQueue") - internal static let closedGroupPollerQueue = DispatchQueue(label: "SessionMessagingKit.closedGroupPollerQueue") - internal static let openGroupPollerQueue = DispatchQueue(label: "SessionMessagingKit.openGroup") } From c85e3ef86b001b17c131e31de02941a83e16baaa Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Fri, 25 Feb 2022 10:57:02 +1100 Subject: [PATCH 25/46] clean up unused code --- .../ConversationVC+Interaction.swift | 5 --- Session/Conversations/ConversationVC.swift | 1 - Session/Conversations/ConversationViewModel.h | 2 -- Session/Conversations/ConversationViewModel.m | 31 ------------------- 4 files changed, 39 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index a802e285b..9548fc766 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -591,11 +591,6 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc } func delete(_ viewItem: ConversationViewItem) { - if (!self.isUnsendRequestsEnabled) { - viewItem.deleteAction() - return - } - guard let message = viewItem.interaction as? TSMessage else { return self.deleteLocally(viewItem) } // Handle open group messages the old way diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 990d2de75..fb647285a 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -5,7 +5,6 @@ // • Remaining search glitchiness final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversationSettingsViewDelegate, ConversationSearchControllerDelegate, UITableViewDataSource, UITableViewDelegate { - let isUnsendRequestsEnabled = true // Set to true once unsend requests are done on all platforms let thread: TSThread let focusedMessageID: String? // This is used for global search var focusedMessageIndexPath: IndexPath? diff --git a/Session/Conversations/ConversationViewModel.h b/Session/Conversations/ConversationViewModel.h index d8d1b21ea..d9642ed4d 100644 --- a/Session/Conversations/ConversationViewModel.h +++ b/Session/Conversations/ConversationViewModel.h @@ -122,8 +122,6 @@ static const int kYapDatabaseRangeMaxLength = 250000; - (void)ensureDynamicInteractionsAndUpdateIfNecessary:(BOOL)updateIfNecessary; -- (void)clearUnreadMessagesIndicator; - - (void)loadAnotherPageOfMessages; - (void)viewDidResetContentAndLayout; diff --git a/Session/Conversations/ConversationViewModel.m b/Session/Conversations/ConversationViewModel.m index 949b5a616..c643871ff 100644 --- a/Session/Conversations/ConversationViewModel.m +++ b/Session/Conversations/ConversationViewModel.m @@ -517,37 +517,6 @@ NS_ASSUME_NONNULL_BEGIN } } -- (void)clearUnreadMessagesIndicator -{ - OWSAssertIsOnMainThread(); - - // TODO: Remove by making unread indicator a view model concern. - id _Nullable oldIndicatorItem = [self.viewState unreadIndicatorViewItem]; - if (oldIndicatorItem) { - // TODO ideally this would be happening within the *same* transaction that caused the unreadMessageIndicator - // to be cleared. - [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [oldIndicatorItem.interaction touchWithTransaction:transaction]; - }]; - } - - if (self.hasClearedUnreadMessagesIndicator) { - // ensureDynamicInteractionsForThread is somewhat expensive - // so we don't want to call it unnecessarily. - return; - } - - // Once we've cleared the unread messages indicator, - // make sure we don't show it again. - self.hasClearedUnreadMessagesIndicator = YES; - - if (self.dynamicInteractions.unreadIndicator) { - // If we've just cleared the "unread messages" indicator, - // update the dynamic interactions. - [self ensureDynamicInteractionsAndUpdateIfNecessary:YES]; - } -} - #pragma mark - Storage access - (void)uiDatabaseDidUpdateExternally:(NSNotification *)notification From 66567ba9f504a40681be5ca95eb271c65a16e2b6 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Fri, 25 Feb 2022 11:50:56 +1100 Subject: [PATCH 26/46] clean --- Session/Conversations/ConversationViewModel.h | 2 +- Session/Conversations/ConversationViewModel.m | 19 ++++++------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/Session/Conversations/ConversationViewModel.h b/Session/Conversations/ConversationViewModel.h index d9642ed4d..36ea8e697 100644 --- a/Session/Conversations/ConversationViewModel.h +++ b/Session/Conversations/ConversationViewModel.h @@ -120,7 +120,7 @@ static const int kYapDatabaseRangeMaxLength = 250000; focusMessageIdOnOpen:(nullable NSString *)focusMessageIdOnOpen delegate:(id)delegate NS_DESIGNATED_INITIALIZER; -- (void)ensureDynamicInteractionsAndUpdateIfNecessary:(BOOL)updateIfNecessary; +- (void)ensureDynamicInteractionsAndUpdateIfNecessary; - (void)loadAnotherPageOfMessages; diff --git a/Session/Conversations/ConversationViewModel.m b/Session/Conversations/ConversationViewModel.m index c643871ff..ac9ba0ccc 100644 --- a/Session/Conversations/ConversationViewModel.m +++ b/Session/Conversations/ConversationViewModel.m @@ -295,13 +295,6 @@ NS_ASSUME_NONNULL_BEGIN object:nil]; } -- (void)signalAccountsDidChange:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - [self ensureDynamicInteractionsAndUpdateIfNecessary:YES]; -} - - (void)profileWhitelistDidChange:(NSNotification *)notification { OWSAssertIsOnMainThread(); @@ -334,7 +327,7 @@ NS_ASSUME_NONNULL_BEGIN self.typingIndicatorsSender = [self.typingIndicators typingRecipientIdForThread:self.thread]; self.collapseCutoffDate = [NSDate new]; - [self ensureDynamicInteractionsAndUpdateIfNecessary:NO]; + [self ensureDynamicInteractionsAndUpdateIfNecessary]; [self.primaryStorage updateUIDatabaseConnectionToLatest]; [self createNewMessageMapping]; @@ -490,7 +483,7 @@ NS_ASSUME_NONNULL_BEGIN self.collapseCutoffDate = [NSDate new]; } -- (void)ensureDynamicInteractionsAndUpdateIfNecessary:(BOOL)updateIfNecessary +- (void)ensureDynamicInteractionsAndUpdateIfNecessary { OWSAssertIsOnMainThread(); @@ -508,7 +501,7 @@ NS_ASSUME_NONNULL_BEGIN BOOL didChange = ![NSObject isNullableObject:self.dynamicInteractions equalTo:dynamicInteractions]; self.dynamicInteractions = dynamicInteractions; - if (didChange && updateIfNecessary) { + if (didChange) { if (![self reloadViewItems]) { OWSFailDebug(@"Failed to reload view items."); } @@ -953,7 +946,7 @@ NS_ASSUME_NONNULL_BEGIN self.collapseCutoffDate = [NSDate new]; - [self ensureDynamicInteractionsAndUpdateIfNecessary:NO]; + [self ensureDynamicInteractionsAndUpdateIfNecessary]; if (![self reloadViewItems]) { OWSFailDebug(@"failed to reload view items in resetMapping."); @@ -1392,7 +1385,7 @@ NS_ASSUME_NONNULL_BEGIN self.collapseCutoffDate = [NSDate new]; - [self ensureDynamicInteractionsAndUpdateIfNecessary:NO]; + [self ensureDynamicInteractionsAndUpdateIfNecessary]; if (![self reloadViewItems]) { OWSFailDebug(@"failed to reload view items in resetMapping."); @@ -1416,7 +1409,7 @@ NS_ASSUME_NONNULL_BEGIN self.collapseCutoffDate = [NSDate new]; - [self ensureDynamicInteractionsAndUpdateIfNecessary:NO]; + [self ensureDynamicInteractionsAndUpdateIfNecessary]; if (![self reloadViewItems]) { OWSFailDebug(@"failed to reload view items in resetMapping."); From 936fbd27c2ed1f2a33fe8173e35717014a4e78f0 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Fri, 25 Feb 2022 15:18:27 +1100 Subject: [PATCH 27/46] fix message request notification --- Session/Notifications/AppNotifications.swift | 2 +- .../NSENotificationPresenter.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index c01df8faf..c4118e3a6 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -171,7 +171,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { let numMessageRequests = threads.numberOfItems(inGroup: TSMessageRequestGroup) // Allow this to show a notification if there are no message requests (ie. this is the first one) - guard numMessageRequests <= 1 else { return } + guard numMessageRequests == 0 else { return } } else if thread.isMessageRequest() && CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] { // If there are other interactions on this thread already then don't show the notification diff --git a/SessionNotificationServiceExtension/NSENotificationPresenter.swift b/SessionNotificationServiceExtension/NSENotificationPresenter.swift index 8cf6eaca8..ea04e9975 100644 --- a/SessionNotificationServiceExtension/NSENotificationPresenter.swift +++ b/SessionNotificationServiceExtension/NSENotificationPresenter.swift @@ -18,7 +18,7 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol { let numMessageRequests = threads.numberOfItems(inGroup: TSMessageRequestGroup) // Allow this to show a notification if there are no message requests (ie. this is the first one) - guard numMessageRequests <= 1 else { return } + guard numMessageRequests == 0 else { return } } else if thread.isMessageRequest() && CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] { // If there are other interactions on this thread already then don't show the notification From 2ae0ae40d4c98eb5994e058bc1daaf3c91488ab2 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 28 Feb 2022 15:22:38 +1100 Subject: [PATCH 28/46] minor improvements to reduce the database read on UI thread --- Session/Conversations/ConversationVC.swift | 19 ++++++----- .../Content Views/QuoteView.swift | 33 +++++-------------- .../Message Cells/VisibleMessageCell.swift | 2 +- 3 files changed, 20 insertions(+), 34 deletions(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 4be6e8d13..e41658545 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -11,6 +11,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat let threadStartedAsMessageRequest: Bool let focusedMessageID: String? // This is used for global search var focusedMessageIndexPath: IndexPath? + var initialUnreadCount: UInt = 0 var unreadViewItems: [ConversationViewItem] = [] var scrollButtonBottomConstraint: NSLayoutConstraint? var scrollButtonMessageRequestsBottomConstraint: NSLayoutConstraint? @@ -276,11 +277,10 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat self.threadStartedAsMessageRequest = thread.isMessageRequest() self.focusedMessageID = focusedMessageID super.init(nibName: nil, bundle: nil) - var unreadCount: UInt = 0 Storage.read { transaction in - unreadCount = self.thread.unreadMessageCount(transaction: transaction) + self.initialUnreadCount = self.thread.unreadMessageCount(transaction: transaction) } - let clampedUnreadCount = min(unreadCount, UInt(kConversationInitialMaxRangeSize), UInt(viewItems.endIndex)) + let clampedUnreadCount = min(self.initialUnreadCount, UInt(kConversationInitialMaxRangeSize), UInt(viewItems.endIndex)) unreadViewItems = clampedUnreadCount != 0 ? [ConversationViewItem](viewItems[viewItems.endIndex - Int(clampedUnreadCount) ..< viewItems.endIndex]) : [] } @@ -289,6 +289,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat } override func viewDidLoad() { + SNLog("Ryan: Conversation VC starts loading at \(NSDate.ows_millisecondTimeStamp())") super.viewDidLoad() // Gradient setUpGradientBackground() @@ -391,27 +392,26 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat if let v2OpenGroup = Storage.shared.getV2OpenGroup(for: thread.uniqueId!) { OpenGroupAPIV2.getMemberCount(for: v2OpenGroup.room, on: v2OpenGroup.server).retainUntilComplete() } + + SNLog("Ryan: Conversation VC ends loading at \(NSDate.ows_millisecondTimeStamp())") } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() if !didFinishInitialLayout { // Scroll to the last unread message if possible; otherwise scroll to the bottom. - var unreadCount: UInt = 0 - Storage.read { transaction in - unreadCount = self.thread.unreadMessageCount(transaction: transaction) - } // When the unread message count is more than the number of view items of a page, // the screen will scroll to the bottom instead of the first unread message. // unreadIndicatorIndex is calculated during loading of the viewItems, so it's // supposed to be accurate. DispatchQueue.main.async { + SNLog("Ryan: Conversation VC starts layout subviews at \(NSDate.ows_millisecondTimeStamp())") if let focusedMessageID = self.focusedMessageID { self.scrollToInteraction(with: focusedMessageID, isAnimated: false, highlighted: true) } else { let firstUnreadMessageIndex = self.viewModel.viewState.unreadIndicatorIndex?.intValue ?? (self.viewItems.count - self.unreadViewItems.count) - if unreadCount > 0, let viewItem = self.viewItems[ifValid: firstUnreadMessageIndex], let interactionID = viewItem.interaction.uniqueId { + if self.initialUnreadCount > 0, let viewItem = self.viewItems[ifValid: firstUnreadMessageIndex], let interactionID = viewItem.interaction.uniqueId { self.scrollToInteraction(with: interactionID, position: .top, isAnimated: false) self.unreadCountView.alpha = self.scrollButton.alpha } else { @@ -419,11 +419,13 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat } } self.scrollButton.alpha = self.getScrollButtonOpacity() + SNLog("Ryan: Conversation VC ends layout subviews at \(NSDate.ows_millisecondTimeStamp())") } } } override func viewDidAppear(_ animated: Bool) { + SNLog("Ryan: Conversation VC did appear at \(NSDate.ows_millisecondTimeStamp())") super.viewDidAppear(animated) highlightFocusedMessageIfNeeded() didFinishInitialLayout = true @@ -777,6 +779,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat let isMainAppAndActive = CurrentAppContext().isMainAppAndActive guard isMainAppAndActive && viewModel.canLoadMoreItems() && !isLoadingMore && messagesTableView.contentOffset.y < ConversationVC.loadMoreThreshold else { return } + print("Ryan: auto loading more") isLoadingMore = true viewModel.loadAnotherPageOfMessages() } diff --git a/Session/Conversations/Message Cells/Content Views/QuoteView.swift b/Session/Conversations/Message Cells/Content Views/QuoteView.swift index a32e07724..efd23a388 100644 --- a/Session/Conversations/Message Cells/Content Views/QuoteView.swift +++ b/Session/Conversations/Message Cells/Content Views/QuoteView.swift @@ -1,6 +1,7 @@ final class QuoteView : UIView { private let mode: Mode + private let thread: TSThread private let direction: Direction private let hInset: CGFloat private let maxWidth: CGFloat @@ -34,25 +35,6 @@ final class QuoteView : UIView { } } - private var threadID: String { - switch mode { - case .regular(let viewItem): return viewItem.interaction.uniqueThreadId - case .draft(let model): return model.threadId - } - } - - private var isGroupThread: Bool { - switch mode { - case .regular(let viewItem): return viewItem.isGroupThread - case .draft(let model): - var result = false - Storage.read { transaction in - result = TSThread.fetch(uniqueId: model.threadId, transaction: transaction)?.isGroupThread() ?? false - } - return result - } - } - private var authorID: String { switch mode { case .regular(let viewItem): return viewItem.quotedReply!.authorId @@ -93,8 +75,9 @@ final class QuoteView : UIView { static let cancelButtonSize: CGFloat = 33 // MARK: Lifecycle - init(for viewItem: ConversationViewItem, direction: Direction, hInset: CGFloat, maxWidth: CGFloat) { + init(for viewItem: ConversationViewItem, in thread: TSThread?, direction: Direction, hInset: CGFloat, maxWidth: CGFloat) { self.mode = .regular(viewItem) + self.thread = thread ?? TSThread.fetch(uniqueId: viewItem.interaction.uniqueThreadId)! self.maxWidth = maxWidth self.direction = direction self.hInset = hInset @@ -105,6 +88,7 @@ final class QuoteView : UIView { init(for model: OWSQuotedReplyModel, direction: Direction, hInset: CGFloat, maxWidth: CGFloat, delegate: QuoteViewDelegate) { self.mode = .draft(model) + self.thread = TSThread.fetch(uniqueId: model.threadId)! self.maxWidth = maxWidth self.direction = direction self.hInset = hInset @@ -188,16 +172,15 @@ final class QuoteView : UIView { bodyLabel.lineBreakMode = .byTruncatingTail let isOutgoing = (direction == .outgoing) bodyLabel.font = .systemFont(ofSize: Values.smallFontSize) - bodyLabel.attributedText = given(body) { MentionUtilities.highlightMentions(in: $0, isOutgoingMessage: isOutgoing, threadID: threadID, attributes: [:]) } - ?? given(attachments.first?.contentType) { NSAttributedString(string: MIMETypeUtil.isAudio($0) ? "Audio" : "Document") } ?? NSAttributedString(string: "Document") + bodyLabel.attributedText = given(body) { MentionUtilities.highlightMentions(in: $0, isOutgoingMessage: isOutgoing, threadID: thread.uniqueId!, attributes: [:]) } ?? given(attachments.first?.contentType) { NSAttributedString(string: MIMETypeUtil.isAudio($0) ? "Audio" : "Document") } ?? NSAttributedString(string: "Document") bodyLabel.textColor = textColor let bodyLabelSize = bodyLabel.systemLayoutSizeFitting(availableSpace) // Label stack view var authorLabelHeight: CGFloat? - if isGroupThread { + if let groupThread = thread as? TSGroupThread { let authorLabel = UILabel() authorLabel.lineBreakMode = .byTruncatingTail - let context: Contact.Context = (TSGroupThread.fetch(uniqueId: threadID)?.isOpenGroup == true) ? .openGroup : .regular + let context: Contact.Context = groupThread.isOpenGroup ? .openGroup : .regular authorLabel.text = Storage.shared.getContact(with: authorID)?.displayName(for: context) ?? authorID authorLabel.textColor = textColor authorLabel.font = .boldSystemFont(ofSize: Values.smallFontSize) @@ -225,7 +208,7 @@ final class QuoteView : UIView { // Constraints contentView.addSubview(mainStackView) mainStackView.pin(to: contentView) - if !isGroupThread { + if !thread.isGroupThread() { bodyLabel.set(.width, to: bodyLabelSize.width) } let bodyLabelHeight = bodyLabelSize.height.clamp(0, maxBodyLabelHeight) diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 19fa3748a..25fdd3dc5 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -335,7 +335,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate { if viewItem.quotedReply != nil { let direction: QuoteView.Direction = isOutgoing ? .outgoing : .incoming let hInset: CGFloat = 2 - let quoteView = QuoteView(for: viewItem, direction: direction, hInset: hInset, maxWidth: maxWidth) + let quoteView = QuoteView(for: viewItem, in: thread, direction: direction, hInset: hInset, maxWidth: maxWidth) let quoteViewContainer = UIView(wrapping: quoteView, withInsets: UIEdgeInsets(top: 0, leading: hInset, bottom: 0, trailing: hInset)) stackView.addArrangedSubview(quoteViewContainer) } From ec40922cb4e0e93cabe193d9369e9e81c3730066 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 28 Feb 2022 15:27:00 +1100 Subject: [PATCH 29/46] reduce initial loading on conversation screen --- Session/Conversations/ConversationVC.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index e41658545..c60ceb578 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -777,9 +777,8 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat func autoLoadMoreIfNeeded() { let isMainAppAndActive = CurrentAppContext().isMainAppAndActive - guard isMainAppAndActive && viewModel.canLoadMoreItems() && !isLoadingMore + guard isMainAppAndActive && didFinishInitialLayout && viewModel.canLoadMoreItems() && !isLoadingMore && messagesTableView.contentOffset.y < ConversationVC.loadMoreThreshold else { return } - print("Ryan: auto loading more") isLoadingMore = true viewModel.loadAnotherPageOfMessages() } From 885ab665501364e231beb0944a9fe956fab9b113 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 28 Feb 2022 16:02:28 +1100 Subject: [PATCH 30/46] minor improvements on scroll to bottom --- Session/Conversations/ConversationVC.swift | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index c60ceb578..fe5a3677f 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -77,7 +77,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat return Mnemonic.encode(hexEncodedString: hexEncodedSeed) }() - lazy var viewModel = ConversationViewModel(thread: thread, focusMessageIdOnOpen: focusedMessageID, delegate: self) + lazy var viewModel = ConversationViewModel(thread: thread, focusMessageIdOnOpen: nil, delegate: self) lazy var mediaCache: NSCache = { let result = NSCache() @@ -515,7 +515,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat // needed for proper calculations, so force an initial layout if it doesn't have a size) var hasDoneLayout: Bool = true - if messageRequestView.bounds.height <= CGFloat.leastNonzeroMagnitude { + if thread.isMessageRequest() && messageRequestView.bounds.height <= CGFloat.leastNonzeroMagnitude { hasDoneLayout = false UIView.performWithoutAnimation { @@ -735,16 +735,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat func scrollToBottom(isAnimated: Bool) { guard !isUserScrolling else { return } - if let interactionID = viewItems.last?.interaction.uniqueId { - self.scrollToInteraction(with: interactionID, position: .top, isAnimated: isAnimated) - return - } - // Ensure the view is fully up to date before we try to scroll to the bottom, since - // we use the table view's bounds to determine where the bottom is. - view.layoutIfNeeded() - let firstContentPageTop: CGFloat = 0 - let contentOffsetY = max(firstContentPageTop, lastPageTop) - messagesTableView.setContentOffset(CGPoint(x: 0, y: contentOffsetY), animated: isAnimated) + messagesTableView.scrollToRow(at: IndexPath(row: viewItems.count - 1, section: 0), at: .top, animated: isAnimated) } func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { From 629d9529445c840dd655b882deb7f7a5f39e3b98 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 28 Feb 2022 16:08:36 +1100 Subject: [PATCH 31/46] clean --- Session/Conversations/ConversationVC.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index fe5a3677f..bff3d309a 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -289,7 +289,6 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat } override func viewDidLoad() { - SNLog("Ryan: Conversation VC starts loading at \(NSDate.ows_millisecondTimeStamp())") super.viewDidLoad() // Gradient setUpGradientBackground() @@ -392,8 +391,6 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat if let v2OpenGroup = Storage.shared.getV2OpenGroup(for: thread.uniqueId!) { OpenGroupAPIV2.getMemberCount(for: v2OpenGroup.room, on: v2OpenGroup.server).retainUntilComplete() } - - SNLog("Ryan: Conversation VC ends loading at \(NSDate.ows_millisecondTimeStamp())") } override func viewDidLayoutSubviews() { @@ -405,7 +402,6 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat // unreadIndicatorIndex is calculated during loading of the viewItems, so it's // supposed to be accurate. DispatchQueue.main.async { - SNLog("Ryan: Conversation VC starts layout subviews at \(NSDate.ows_millisecondTimeStamp())") if let focusedMessageID = self.focusedMessageID { self.scrollToInteraction(with: focusedMessageID, isAnimated: false, highlighted: true) } else { @@ -419,13 +415,11 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat } } self.scrollButton.alpha = self.getScrollButtonOpacity() - SNLog("Ryan: Conversation VC ends layout subviews at \(NSDate.ows_millisecondTimeStamp())") } } } override func viewDidAppear(_ animated: Bool) { - SNLog("Ryan: Conversation VC did appear at \(NSDate.ows_millisecondTimeStamp())") super.viewDidAppear(animated) highlightFocusedMessageIfNeeded() didFinishInitialLayout = true From 27e7c25197d6372298b4a1caaf660660fdcb0425 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 28 Feb 2022 16:35:28 +1100 Subject: [PATCH 32/46] fix scroll to bottom crash --- Session/Conversations/ConversationVC.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index bff3d309a..c2868ff06 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -728,7 +728,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat } func scrollToBottom(isAnimated: Bool) { - guard !isUserScrolling else { return } + guard !isUserScrolling && !viewItems.isEmpty else { return } messagesTableView.scrollToRow(at: IndexPath(row: viewItems.count - 1, section: 0), at: .top, animated: isAnimated) } From ccae9141ff285dd7ce39016126d69ce06d821937 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Tue, 1 Mar 2022 10:45:34 +1100 Subject: [PATCH 33/46] rollback to fix an odd scrolling issue --- Session/Conversations/ConversationVC.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 62cba5a5f..e36fa49f3 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -527,7 +527,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat // needed for proper calculations, so force an initial layout if it doesn't have a size) var hasDoneLayout: Bool = true - if thread.isMessageRequest() && messageRequestView.bounds.height <= CGFloat.leastNonzeroMagnitude { + if messageRequestView.bounds.height <= CGFloat.leastNonzeroMagnitude { hasDoneLayout = false UIView.performWithoutAnimation { From 78e2c4f55c0c896c3cdf6abb8ff073e4fc349b93 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Tue, 1 Mar 2022 11:21:07 +1100 Subject: [PATCH 34/46] minor refactor in search bar & message request vc --- Session/Conversations/ConversationVC.swift | 22 +------------------ .../MessageRequestsViewController.swift | 21 ++++++------------ SessionUIKit/Components/SearchBar.swift | 13 ++++++----- 3 files changed, 16 insertions(+), 40 deletions(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index e36fa49f3..75286ab90 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -820,28 +820,8 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat func showSearchUI() { isShowingSearchUI = true // Search bar - // FIXME: This code is duplicated with SearchBar let searchBar = searchController.uiSearchController.searchBar - searchBar.searchBarStyle = .minimal - searchBar.barStyle = .black - searchBar.tintColor = Colors.text - let searchIcon = UIImage(named: "searchbar_search")!.asTintedImage(color: Colors.searchBarPlaceholder) - searchBar.setImage(searchIcon, for: .search, state: UIControl.State.normal) - let clearIcon = UIImage(named: "searchbar_clear")!.asTintedImage(color: Colors.searchBarPlaceholder) - searchBar.setImage(clearIcon, for: .clear, state: UIControl.State.normal) - let searchTextField: UITextField - if #available(iOS 13, *) { - searchTextField = searchBar.searchTextField - } else { - searchTextField = searchBar.value(forKey: "_searchField") as! UITextField - } - searchTextField.backgroundColor = Colors.searchBarBackground - searchTextField.textColor = Colors.text - searchTextField.attributedPlaceholder = NSAttributedString(string: "Search", attributes: [ .foregroundColor : Colors.searchBarPlaceholder ]) - searchTextField.keyboardAppearance = isLightMode ? .default : .dark - searchBar.setPositionAdjustment(UIOffset(horizontal: 4, vertical: 0), for: .search) - searchBar.searchTextPositionAdjustment = UIOffset(horizontal: 2, vertical: 0) - searchBar.setPositionAdjustment(UIOffset(horizontal: -4, vertical: 0), for: .clear) + searchBar.setUpSessionStyle() navigationItem.titleView = searchBar // Nav bar buttons updateNavBarButtons() diff --git a/Session/Home/Message Requests/MessageRequestsViewController.swift b/Session/Home/Message Requests/MessageRequestsViewController.swift index 32e1fc46e..4ecc55ea1 100644 --- a/Session/Home/Message Requests/MessageRequestsViewController.swift +++ b/Session/Home/Message Requests/MessageRequestsViewController.swift @@ -6,7 +6,11 @@ import SessionMessagingKit @objc class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDataSource { - private var threads: YapDatabaseViewMappings! + private var threads: YapDatabaseViewMappings! = { + let result = YapDatabaseViewMappings(groups: [ TSMessageRequestGroup ], view: TSThreadDatabaseViewExtensionName) + result.setIsReversed(true, forGroup: TSMessageRequestGroup) + return result + }() private var threadViewModelCache: [String: ThreadViewModel] = [:] // Thread ID to ThreadViewModel private var tableViewTopConstraint: NSLayoutConstraint! @@ -85,16 +89,13 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat ViewControllerUtilities.setUpDefaultSessionStyle(for: self, title: NSLocalizedString("MESSAGE_REQUESTS_TITLE", comment: ""), hasCustomBackButton: false) - // Threads (part 1) - // Freeze the connection for use on the main thread (this gives us a stable data source that doesn't change until we tell it to) - dbConnection.beginLongLivedReadTransaction() - // Add the UI (MUST be done after the thread freeze so the 'tableView' creation and setting // the dataSource has the correct data) view.addSubview(tableView) view.addSubview(emptyStateLabel) view.addSubview(fadeView) view.addSubview(clearAllButton) + setupLayout() // Notifications NotificationCenter.default.addObserver( @@ -116,18 +117,11 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat object: nil ) - // Threads (part 2) - threads = YapDatabaseViewMappings(groups: [ TSMessageRequestGroup ], view: TSThreadDatabaseViewExtensionName) // The extension should be registered at this point - dbConnection.read { transaction in - self.threads.update(with: transaction) // Perform the initial update - } - - setupLayout() + reload() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - reload() } @@ -189,7 +183,6 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat tableView.reloadData() clearAllButton.isHidden = (messageRequestCount == 0) emptyStateLabel.isHidden = (messageRequestCount != 0) - emptyStateLabel.isHidden = (messageRequestCount != 0) } @objc private func handleYapDatabaseModifiedNotification(_ yapDatabase: YapDatabase) { diff --git a/SessionUIKit/Components/SearchBar.swift b/SessionUIKit/Components/SearchBar.swift index e4748b0ba..33a13e14c 100644 --- a/SessionUIKit/Components/SearchBar.swift +++ b/SessionUIKit/Components/SearchBar.swift @@ -4,18 +4,21 @@ public final class SearchBar : UISearchBar { public override init(frame: CGRect) { super.init(frame: frame) - setUpStyle() + setUpSessionStyle() } public required init?(coder: NSCoder) { super.init(coder: coder) - setUpStyle() + setUpSessionStyle() } - - private func setUpStyle() { +} + +public extension UISearchBar { + + func setUpSessionStyle() { searchBarStyle = .minimal // Hide the border around the search bar barStyle = .black // Use Apple's black design as a base - tintColor = Colors.accent // The cursor color + tintColor = Colors.text // The cursor color let searchImage = #imageLiteral(resourceName: "searchbar_search").withTint(Colors.searchBarPlaceholder)! setImage(searchImage, for: .search, state: .normal) let clearImage = #imageLiteral(resourceName: "searchbar_clear").withTint(Colors.searchBarPlaceholder)! From 02cc97a2385552a0dbe06187323bed8400ea4dad Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Tue, 1 Mar 2022 11:34:23 +1100 Subject: [PATCH 35/46] Change Session title in dark mode to white --- Session/Shared/BaseVC.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Session/Shared/BaseVC.swift b/Session/Shared/BaseVC.swift index b0692e5b7..b2953ed36 100644 --- a/Session/Shared/BaseVC.swift +++ b/Session/Shared/BaseVC.swift @@ -84,7 +84,7 @@ class BaseVC : UIViewController { internal func setUpNavBarSessionHeading() { let headingImageView = UIImageView() - headingImageView.tintColor = Colors.sessionHeading + headingImageView.tintColor = Colors.text headingImageView.image = UIImage(named: "SessionHeading")?.withRenderingMode(.alwaysTemplate) headingImageView.contentMode = .scaleAspectFit headingImageView.set(.width, to: 150) From 821bd4cadbd17bb732c7de0489796c11471e2d39 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Tue, 1 Mar 2022 11:46:29 +1100 Subject: [PATCH 36/46] fix visible message bubble round corner issue --- Session/Conversations/Message Cells/VisibleMessageCell.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 25fdd3dc5..c0c5512c1 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -322,10 +322,12 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate { linkPreviewView.linkPreviewState = LinkPreviewSent(linkPreview: linkPreview, imageAttachment: viewItem.linkPreviewAttachment) snContentView.addSubview(linkPreviewView) linkPreviewView.pin(to: snContentView) + linkPreviewView.layer.mask = bubbleViewMaskLayer } else if let openGroupInvitationName = message.openGroupInvitationName, let openGroupInvitationURL = message.openGroupInvitationURL { let openGroupInvitationView = OpenGroupInvitationView(name: openGroupInvitationName, url: openGroupInvitationURL, textColor: bodyLabelTextColor, isOutgoing: isOutgoing) snContentView.addSubview(openGroupInvitationView) openGroupInvitationView.pin(to: snContentView) + openGroupInvitationView.layer.mask = bubbleViewMaskLayer } else { // Stack view let stackView = UIStackView(arrangedSubviews: []) @@ -389,6 +391,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate { let voiceMessageView = VoiceMessageView(viewItem: viewItem) snContentView.addSubview(voiceMessageView) voiceMessageView.pin(to: snContentView) + voiceMessageView.layer.mask = bubbleViewMaskLayer viewItem.lastAudioMessageView = voiceMessageView } case .genericAttachment: From 6b231316eed9d638d9ab7fb47825891886387e74 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Tue, 1 Mar 2022 13:22:53 +1100 Subject: [PATCH 37/46] fix & improve voice message preview in home screen --- .../Sending & Receiving/Attachments/TSAttachment.m | 2 +- SessionMessagingKit/Sending & Receiving/MessageSender.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/SessionMessagingKit/Sending & Receiving/Attachments/TSAttachment.m b/SessionMessagingKit/Sending & Receiving/Attachments/TSAttachment.m index f3722bd80..3d92cdca8 100644 --- a/SessionMessagingKit/Sending & Receiving/Attachments/TSAttachment.m +++ b/SessionMessagingKit/Sending & Receiving/Attachments/TSAttachment.m @@ -185,7 +185,7 @@ NSUInteger const TSAttachmentSchemaVersion = 4; if (self.isVoiceMessage || !self.sourceFilename || self.sourceFilename.length == 0) { attachmentString = NSLocalizedString(@"ATTACHMENT_TYPE_VOICE_MESSAGE", @"Short text label for a voice message attachment, used for thread preview and on the lock screen"); - return [NSString stringWithFormat:@"🎤 %@", attachmentString]; + return [NSString stringWithFormat:@"🎙️ %@", attachmentString]; } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index fcb4f1a54..dd44c8197 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -59,6 +59,7 @@ public final class MessageSender : NSObject { signalAttachments.forEach { let attachment = TSAttachmentStream(contentType: $0.mimeType, byteCount: UInt32($0.dataLength), sourceFilename: $0.sourceFilename, caption: $0.captionText, albumMessageId: tsMessage.uniqueId!) + attachment.attachmentType = $0.isVoiceMessage ? .voiceMessage : .default attachments.append(attachment) attachment.write($0.dataSource) attachment.save(with: transaction) From 279892e83bd2bc98103676aaf9d63e8a84d5e9d0 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Tue, 1 Mar 2022 14:03:10 +1100 Subject: [PATCH 38/46] fix some scrolling to bottom and button issue --- Session/Conversations/ConversationVC.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 75286ab90..1ec8000e1 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -644,9 +644,6 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat } self.markAllAsRead() } - if shouldScrollToBottom { - self.scrollToBottom(isAnimated: false) - } } // Update the input state if this is a contact thread @@ -747,7 +744,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat func scrollToBottom(isAnimated: Bool) { guard !isUserScrolling && !viewItems.isEmpty else { return } - messagesTableView.scrollToRow(at: IndexPath(row: viewItems.count - 1, section: 0), at: .top, animated: isAnimated) + messagesTableView.scrollToRow(at: IndexPath(row: viewItems.count - 1, section: 0), at: .bottom, animated: isAnimated) } func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { From e014fe0367cb97772bd51a60187a52976d967ef5 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Tue, 1 Mar 2022 16:49:59 +1100 Subject: [PATCH 39/46] prevent the message bubble not being wide enough to show the corner radius for very short message --- Session/Conversations/Message Cells/VisibleMessageCell.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index c0c5512c1..bbcd2679a 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -66,6 +66,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate { lazy var bubbleView: UIView = { let result = UIView() result.layer.cornerRadius = VisibleMessageCell.largeCornerRadius + result.set(.width, greaterThanOrEqualTo: VisibleMessageCell.largeCornerRadius * 2) return result }() From da66b1af2ca40779ee9bb2f9b9d5575d4fb91c74 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Tue, 1 Mar 2022 16:51:16 +1100 Subject: [PATCH 40/46] fix swiping back for approved message request conversation --- .../ConversationVC+Interaction.swift | 26 +++++++------------ Session/Conversations/ConversationVC.swift | 2 -- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index c080f3d86..f6be9fd9a 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1097,22 +1097,6 @@ extension ConversationVC: UIDocumentInteractionControllerDelegate { // MARK: - Message Request Actions extension ConversationVC { - @objc func handleBackPressed() { - // If this thread started as a message request but isn't one anymore then we want to skip the - // `MessageRequestsViewController` when going back - guard - threadStartedAsMessageRequest, - !thread.isMessageRequest(), - let viewControllers: [UIViewController] = navigationController?.viewControllers, - let messageRequestsIndex = viewControllers.firstIndex(where: { $0 is MessageRequestsViewController }), - messageRequestsIndex > 0 - else { - navigationController?.popViewController(animated: true) - return - } - - navigationController?.popToViewController(viewControllers[messageRequestsIndex - 1], animated: true) - } fileprivate func approveMessageRequestIfNeeded(for thread: TSThread?, with transaction: YapDatabaseReadWriteTransaction, isNewThread: Bool, timestamp: UInt64) { guard let contactThread: TSContactThread = thread as? TSContactThread else { return } @@ -1164,6 +1148,16 @@ extension ConversationVC { ) } } + + // Update UI + self?.updateNavBarButtons() + if let viewControllers: [UIViewController] = self?.navigationController?.viewControllers, + let messageRequestsIndex = viewControllers.firstIndex(where: { $0 is MessageRequestsViewController }), + messageRequestsIndex > 0 { + var newViewControllers = viewControllers + newViewControllers.remove(at: messageRequestsIndex) + self?.navigationController?.setViewControllers(newViewControllers, animated: false) + } // Send a sync message with the details of the contact if let appDelegate = UIApplication.shared.delegate as? AppDelegate { diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 1ec8000e1..f7e1d77cd 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -469,8 +469,6 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat navigationItem.rightBarButtonItems = [] } else { - navigationItem.leftBarButtonItem = UIViewController.createOWSBackButton(withTarget: self, selector: #selector(handleBackPressed)) - if let contactThread: TSContactThread = thread as? TSContactThread { // Don't show the settings button for message requests if let contact: Contact = Storage.shared.getContact(with: contactThread.contactSessionID()), contact.isApproved, contact.didApproveMe { From 1609812e5b1dd2fb17141fea492167ed4f7a9b87 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Tue, 1 Mar 2022 17:15:38 +1100 Subject: [PATCH 41/46] remove useless expiration flag in data message --- .../Messages/Visible Messages/VisibleMessage.swift | 8 -------- 1 file changed, 8 deletions(-) diff --git a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift index 49e70e2c8..21c1a41be 100644 --- a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift +++ b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift @@ -103,14 +103,6 @@ public final class VisibleMessage : Message { // TODO: Contact // Open group invitation if let openGroupInvitation = openGroupInvitation, let openGroupInvitationProto = openGroupInvitation.toProto() { dataMessage.setOpenGroupInvitation(openGroupInvitationProto) } - // Expiration timer - // TODO: We * want * expiration timer updates to be explicit. But currently Android will disable the expiration timer for a conversation - // if it receives a message without the current expiration timer value attached to it... - var expiration: UInt32 = 0 - if let disappearingMessagesConfiguration = OWSDisappearingMessagesConfiguration.fetch(uniqueId: threadID!, transaction: transaction) { - expiration = disappearingMessagesConfiguration.isEnabled ? disappearingMessagesConfiguration.durationSeconds : 0 - } - dataMessage.setExpireTimer(expiration) // Group context do { try setGroupContextIfNeeded(on: dataMessage, using: transaction) From f632763eee85d2651d7e105a8700be550f0a8e79 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Wed, 2 Mar 2022 09:41:23 +1100 Subject: [PATCH 42/46] revert debug code --- .../Sending & Receiving/Notifications/PushNotificationAPI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index c44649c60..8fccb96ec 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -5,7 +5,7 @@ import PromiseKit public final class PushNotificationAPI : NSObject { // MARK: Settings - public static let server = "https://dev.apns.getsession.org" + public static let server = "https://live.apns.getsession.org" public static let serverPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" private static let maxRetryCount: UInt = 4 private static let tokenExpirationInterval: TimeInterval = 12 * 60 * 60 From f7bfeb6e6d0cda7061d340a153ab75e3bd2b0edf Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Wed, 2 Mar 2022 09:42:13 +1100 Subject: [PATCH 43/46] fix typo --- .../NotificationServiceExtension.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 7ffb87888..d35de1ffe 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -30,7 +30,7 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension // Handle the push notification AppReadiness.runNowOrWhenAppDidBecomeReady { - let openGorupPollingPromises = self.pollForOpneGorups() + let openGorupPollingPromises = self.pollForOpenGroups() defer { when(resolved: openGorupPollingPromises).done { _ in self.completeSilenty() @@ -173,7 +173,7 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension } // MARK: Poll for open groups - private func pollForOpneGorups() -> [Promise] { + private func pollForOpenGroups() -> [Promise] { var promises: [Promise] = [] let servers = Set(Storage.shared.getAllV2OpenGroups().values.map { $0.server }) servers.forEach { server in From 3ab7192b261807f62a076654940ac42a5bc0ffcb Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Wed, 2 Mar 2022 11:23:13 +1100 Subject: [PATCH 44/46] clean up some code related to unread message (requests) count --- .../Messaging/OWSMessageUtils.m | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/SignalUtilitiesKit/Messaging/OWSMessageUtils.m b/SignalUtilitiesKit/Messaging/OWSMessageUtils.m index b07a80bf8..e50ac05ec 100644 --- a/SignalUtilitiesKit/Messaging/OWSMessageUtils.m +++ b/SignalUtilitiesKit/Messaging/OWSMessageUtils.m @@ -91,6 +91,7 @@ NS_ASSUME_NONNULL_BEGIN NSLog(@"Found an already read message in the * unread * messages list."); return; } + // We have to filter those unread messages for groups that only notifiy for mentions if ([object isKindOfClass:TSIncomingMessage.class] && isGroupThread) { TSIncomingMessage *incomingMessage = (TSIncomingMessage *)object; if (((TSGroupThread *)thread).isOnlyNotifyingForMentions && !incomingMessage.isUserMentioned) { @@ -103,13 +104,6 @@ NS_ASSUME_NONNULL_BEGIN }]; return count; - - __block NSUInteger numberOfItems; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - numberOfItems = [[transaction ext:TSUnreadDatabaseViewExtensionName] numberOfItemsInAllGroups]; - }]; - - return numberOfItems; } - (NSUInteger)unreadMessageRequestCount { @@ -124,20 +118,9 @@ NS_ASSUME_NONNULL_BEGIN // Only increase the count for message requests if (!thread.isMessageRequest) { continue; } - - [unreadMessages enumerateKeysAndObjectsInGroup:groupID - usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { - if (![object conformsToProtocol:@protocol(OWSReadTracking)]) { - return; - } - id unread = (id)object; - if (unread.read) { - NSLog(@"Found an already read message in the * unread * messages list."); - return; - } + if ([unreadMessages numberOfItemsInGroup:groupID] > 0) { count += 1; - *stop = YES; - }]; + } } }]; From da298756f812062f73f6aefc2361e03498805732 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Tue, 8 Mar 2022 16:31:21 +1100 Subject: [PATCH 45/46] bump up version & build number --- Session.xcodeproj/project.pbxproj | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index f724e0516..cc85683be 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -4669,7 +4669,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C3AABDDF2553ECF00042FF4C /* Array+Description.swift in Sources */, + C3AABDDF2553ECF00042FF4C /* Array+Utilities.swift in Sources */, 7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */, C3AABDDF2553ECF00042FF4C /* Array+Utilities.swift in Sources */, C32C5A47256DB8F0003C73A2 /* ECKeyPair+Hexadecimal.swift in Sources */, @@ -5203,7 +5203,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 319; + CURRENT_PROJECT_VERSION = 322; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5228,7 +5228,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.11.21; + MARKETING_VERSION = 1.11.22; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5276,7 +5276,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 319; + CURRENT_PROJECT_VERSION = 322; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5306,7 +5306,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.11.21; + MARKETING_VERSION = 1.11.22; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5342,7 +5342,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 319; + CURRENT_PROJECT_VERSION = 322; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5365,7 +5365,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.11.21; + MARKETING_VERSION = 1.11.22; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -5416,7 +5416,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 319; + CURRENT_PROJECT_VERSION = 322; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5444,7 +5444,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.11.21; + MARKETING_VERSION = 1.11.22; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -6352,7 +6352,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 319; + CURRENT_PROJECT_VERSION = 322; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6391,7 +6391,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.11.21; + MARKETING_VERSION = 1.11.22; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; @@ -6423,7 +6423,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 319; + CURRENT_PROJECT_VERSION = 322; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6462,7 +6462,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.11.21; + MARKETING_VERSION = 1.11.22; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = Session; From ce95b97f027d42736896e53e01dd8036a3ba8c4a Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Tue, 8 Mar 2022 17:02:34 +1100 Subject: [PATCH 46/46] fix CFBundleIdentifier Collision --- Session.xcodeproj/project.pbxproj | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index cc85683be..be9bec703 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -790,8 +790,7 @@ FD859F0027C4691300510D0C /* MockDataGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EFF27C4691300510D0C /* MockDataGenerator.swift */; }; FD88BAD927A7439C00BBC442 /* MessageRequestsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */; }; FD88BADB27A750F200BBC442 /* MessageRequestsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */; }; - FDC4389E27BA2B8A00C60D73 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; platformFilter = ios; }; - FDC4389F27BA2B8A00C60D73 /* SessionUtilitiesKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + FDC4389E27BA2B8A00C60D73 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -944,17 +943,6 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; - FDC438A227BA2B8A00C60D73 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - FDC4389F27BA2B8A00C60D73 /* SessionUtilitiesKit.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -3961,7 +3949,6 @@ C3C2A6EC25539DE700C340D1 /* Sources */, C3C2A6ED25539DE700C340D1 /* Frameworks */, C3C2A6EE25539DE700C340D1 /* Resources */, - FDC438A227BA2B8A00C60D73 /* Embed Frameworks */, ); buildRules = ( ); @@ -5134,7 +5121,6 @@ }; FDC438A127BA2B8A00C60D73 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - platformFilter = ios; target = C3C2A678255388CC00C340D1 /* SessionUtilitiesKit */; targetProxy = FDC438A027BA2B8A00C60D73 /* PBXContainerItemProxy */; };