diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 4a10b9daf..fee9f747f 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5063,7 +5063,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 279; + CURRENT_PROJECT_VERSION = 284; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5132,7 +5132,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 279; + CURRENT_PROJECT_VERSION = 284; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5193,7 +5193,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 279; + CURRENT_PROJECT_VERSION = 284; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5263,7 +5263,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 279; + CURRENT_PROJECT_VERSION = 284; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6148,7 +6148,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 279; + CURRENT_PROJECT_VERSION = 284; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6216,7 +6216,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 279; + CURRENT_PROJECT_VERSION = 284; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 89ae4dc4e..01d8b320a 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -689,7 +689,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc return cancelVoiceMessageRecording() } // Limit voice messages to a minute - audioTimer = Timer.scheduledTimer(withTimeInterval: 60, repeats: false, block: { [weak self] _ in + audioTimer = Timer.scheduledTimer(withTimeInterval: 180, repeats: false, block: { [weak self] _ in self?.snInputView.hideVoiceMessageUI() self?.endVoiceMessageRecording() }) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index ab678dec6..19a5fd4ed 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -55,6 +55,11 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat return messagesTableView.contentSize.height - tableViewUnobscuredHeight } + var isCloseToBottom: Bool { + let margin = (self.lastPageTop - self.messagesTableView.contentOffset.y) + return margin <= ConversationVC.scrollToBottomMargin + } + lazy var mnemonic: String = { let identityManager = OWSIdentityManager.shared() let databaseConnection = identityManager.value(forKey: "dbConnection") as! YapDatabaseConnection @@ -150,7 +155,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat /// The button will be invisible until the user has scrolled at least this amount from the bottom of the table view. static let scrollButtonNoVisibilityThreshold: CGFloat = 20 /// Automatically scroll to the bottom of the conversation when sending a message if the scroll distance from the bottom is less than this number. - static let scrollToBottomMargin: CGFloat = 40 + static let scrollToBottomMargin: CGFloat = 60 // MARK: Lifecycle init(thread: TSThread, focusedMessageID: String? = nil) { @@ -314,6 +319,13 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat baselineKeyboardHeight = newHeight self.messagesTableView.keyboardHeight = newHeight } + let margin = (self.lastPageTop - self.messagesTableView.contentOffset.y) + // HACK: If the keyboard is coming up and we're very close to the bottom, scroll to the + // bottom. This "fixes" an issue where the conversation would randomly scroll up sometimes + // when bringing up the keyboard. + if newHeight > 200 && margin <= 2 { + scrollToBottom(isAnimated: false) + } scrollButtonConstraint?.constant = -(newHeight + 16) let newContentOffsetY = max(self.messagesTableView.contentOffset.y + min(lastPageTop, 0) + newHeight - self.messagesTableView.keyboardHeight, 0.0) self.messagesTableView.contentOffset.y = newContentOffsetY @@ -353,13 +365,11 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat if update.viewItem?.interaction is TSOutgoingMessage { shouldScrollToBottom = true } else { - let margin = (self.lastPageTop - self.messagesTableView.contentOffset.y) - shouldScrollToBottom = margin <= ConversationVC.scrollToBottomMargin + shouldScrollToBottom = self.isCloseToBottom } case .update: self.messagesTableView.reloadRows(at: [ IndexPath(row: Int(update.oldIndex), section: 0) ], with: .fade) - let margin = (self.lastPageTop - self.messagesTableView.contentOffset.y) - shouldScrollToBottom = margin <= ConversationVC.scrollToBottomMargin + shouldScrollToBottom = self.isCloseToBottom default: preconditionFailure() } } diff --git a/Session/Conversations/Settings/OWSConversationSettingsViewController.m b/Session/Conversations/Settings/OWSConversationSettingsViewController.m index 4f54af7c5..3d303ca9d 100644 --- a/Session/Conversations/Settings/OWSConversationSettingsViewController.m +++ b/Session/Conversations/Settings/OWSConversationSettingsViewController.m @@ -546,7 +546,7 @@ CGFloat kIconViewLength = 24; [topRow autoPinEdgesToSuperviewMarginsExcludingEdge:ALEdgeBottom]; UILabel *subtitleLabel = [UILabel new]; - subtitleLabel.text = NSLocalizedString(@"When enabled, only messages mentioned you will be notified.", @""); + subtitleLabel.text = NSLocalizedString(@"vc_conversation_settings_notify_for_mentions_only_explanation", @""); subtitleLabel.textColor = LKColors.text; subtitleLabel.font = [UIFont systemFontOfSize:LKValues.smallFontSize]; subtitleLabel.numberOfLines = 0; diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index 594430aeb..ce8f3ad91 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -168,8 +168,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { // Don't fire the notification if the current user isn't mentioned // and isOnlyNotifyingForMentions is on. - let isUserMentioned = MentionUtilities.isUserMentioned(in: messageText ?? "") - if let groupThread = thread as? TSGroupThread, groupThread.isOnlyNotifyingForMentions && !isUserMentioned { + if let groupThread = thread as? TSGroupThread, groupThread.isOnlyNotifyingForMentions && !incomingMessage.isUserMentioned { return } diff --git a/Session/Shared/ConversationCell.swift b/Session/Shared/ConversationCell.swift index 46074851b..3b1d8ca6a 100644 --- a/Session/Shared/ConversationCell.swift +++ b/Session/Shared/ConversationCell.swift @@ -157,8 +157,7 @@ final class ConversationCell : UITableViewCell { // MARK: Updating private func update() { AssertIsOnMainThread() - guard let thread = threadViewModel?.threadRecord, let threadID = thread.uniqueId else { return } - MentionsManager.populateUserPublicKeyCacheIfNeeded(for: threadID) // FIXME: This is a terrible place to do this + guard let thread = threadViewModel?.threadRecord else { return } let isBlocked: Bool if let thread = thread as? TSContactThread { isBlocked = SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(thread.contactSessionID()) diff --git a/SessionMessagingKit/Messages/Signal/TSIncomingMessage.h b/SessionMessagingKit/Messages/Signal/TSIncomingMessage.h index bb909b0fc..7cd8dd78b 100644 --- a/SessionMessagingKit/Messages/Signal/TSIncomingMessage.h +++ b/SessionMessagingKit/Messages/Signal/TSIncomingMessage.h @@ -16,6 +16,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) BOOL wasReceivedByUD; +@property (nonatomic, readonly) BOOL isUserMentioned; + - (instancetype)initMessageWithTimestamp:(uint64_t)timestamp inThread:(nullable TSThread *)thread messageBody:(nullable NSString *)body diff --git a/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m b/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m index 81bf7beb1..a7e004b93 100644 --- a/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m +++ b/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m @@ -13,6 +13,7 @@ #import "TSGroupThread.h" #import #import +#import NS_ASSUME_NONNULL_BEGIN @@ -121,6 +122,12 @@ NS_ASSUME_NONNULL_BEGIN return self.isExpiringMessage; } +- (BOOL)isUserMentioned +{ + NSString *userPublicKey = [SNGeneralUtilities getUserPublicKey]; + return (self.body != nil && [self.body containsString:[NSString stringWithFormat:@"@%@", userPublicKey]]) || (self.quotedMessage != nil && [self.quotedMessage.authorId isEqualToString:userPublicKey]); +} + #pragma mark - OWSReadTracking - (BOOL)shouldAffectUnreadCounts diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index ffebdd830..b6e2550a8 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -288,7 +288,11 @@ extension MessageReceiver { // Notify the user if needed guard (isMainAppAndActive || isBackgroundPoll), let tsIncomingMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) as? TSIncomingMessage, let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return tsMessageID } - SSKEnvironment.shared.notificationsManager!.notifyUser(for: tsIncomingMessage, in: thread, transaction: transaction) + DispatchQueue.main.async { + Storage.read { transaction in + SSKEnvironment.shared.notificationsManager!.notifyUser(for: tsIncomingMessage, in: thread, transaction: transaction) + } + } return tsMessageID } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index 2765b541a..025af2aba 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -7,8 +7,8 @@ public final class ClosedGroupPoller : NSObject { private var timers: [String:Timer] = [:] // MARK: Settings - private static let minPollInterval: Double = 4 - private static let maxPollInterval: Double = 2 * 60 + private static let minPollInterval: Double = 2 + private static let maxPollInterval: Double = 30 // MARK: Error private enum Error : LocalizedError { diff --git a/SignalUtilitiesKit/Messaging/OWSMessageUtils.m b/SignalUtilitiesKit/Messaging/OWSMessageUtils.m index 7c0e20575..9f7d6ea41 100644 --- a/SignalUtilitiesKit/Messaging/OWSMessageUtils.m +++ b/SignalUtilitiesKit/Messaging/OWSMessageUtils.m @@ -72,11 +72,13 @@ NS_ASSUME_NONNULL_BEGIN [LKStorage readWithBlock:^(YapDatabaseReadTransaction *transaction) { YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName]; NSArray *allGroups = [unreadMessages allGroups]; + // FIXME: Confusingly, `allGroups` includes contact threads as well for (NSString *groupID in allGroups) { TSThread *thread = [TSThread fetchObjectWithUniqueID:groupID transaction:transaction]; - if (thread.isMuted) continue; + if (thread.isMuted) { continue; } + BOOL isGroupThread = thread.isGroupThread; [unreadMessages enumerateKeysAndObjectsInGroup:groupID - usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { + usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { if (![object conformsToProtocol:@protocol(OWSReadTracking)]) { return; } @@ -85,6 +87,12 @@ NS_ASSUME_NONNULL_BEGIN NSLog(@"Found an already read message in the * unread * messages list."); return; } + if ([object isKindOfClass:TSIncomingMessage.class] && isGroupThread) { + TSIncomingMessage *incomingMessage = (TSIncomingMessage *)object; + if (((TSGroupThread *)thread).isOnlyNotifyingForMentions && !incomingMessage.isUserMentioned) { + return; + } + } count += 1; }]; } diff --git a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift index c1485ad68..b91303954 100644 --- a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift +++ b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift @@ -69,7 +69,7 @@ public final class ProfilePictureView : UIView { publicKey = "" useFallbackPicture = true } else { // A closed group - var users = MentionsManager.userPublicKeyCache[thread.uniqueId!] ?? [] + var users = Set(thread.groupModel.groupMemberIds) users.remove(getUserHexEncodedPublicKey()) var randomUsers = users.sorted() // Sort to provide a level of stability if users.count == 1 {