diff --git a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m index ea8e7fe3e..f7c0c053d 100644 --- a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m +++ b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m @@ -2184,6 +2184,9 @@ typedef enum : NSUInteger { header:(JSQMessagesLoadEarlierHeaderView *)headerView didTapLoadEarlierMessagesButton:(UIButton *)sender { + OWSAssert(!self.isUserScrolling); + + BOOL hasEarlierUnseenMessages = self.dynamicInteractions.hasMoreUnseenMessages; // We want to restore the current scroll state after we update the range, update // the dynamic interactions and re-layout. Here we take a "before" snapshot. @@ -2229,13 +2232,19 @@ typedef enum : NSUInteger { [self.collectionView layoutSubviews]; self.collectionView.contentOffset = CGPointMake(0, self.collectionView.contentSize.height - scrollDistanceToBottom); + [self.scrollLaterTimer invalidate]; - // We want to scroll to the bottom _after_ the layout has been updated. - self.scrollLaterTimer = [NSTimer weakScheduledTimerWithTimeInterval:0.001f - target:self - selector:@selector(scrollToUnreadIndicatorAnimated) - userInfo:nil - repeats:NO]; + // Don’t auto-scroll after “loading more messages” unless we have “more unseen messages”. + // + // Otherwise, tapping on "load more messages" autoscrolls you downward which is completely wrong. + if (hasEarlierUnseenMessages) { + // We want to scroll to the bottom _after_ the layout has been updated. + self.scrollLaterTimer = [NSTimer weakScheduledTimerWithTimeInterval:0.001f + target:self + selector:@selector(scrollToUnreadIndicatorAnimated) + userInfo:nil + repeats:NO]; + } [self updateLoadEarlierVisible]; } diff --git a/Signal/src/util/ThreadUtil.h b/Signal/src/util/ThreadUtil.h index a79199b57..41c84ab78 100644 --- a/Signal/src/util/ThreadUtil.h +++ b/Signal/src/util/ThreadUtil.h @@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN // This is used by MessageViewController to increase the // range size of the mappings (the load window of the conversation) // to include the unread indicator. -@property (nonatomic, nullable) NSNumber *unreadIndicatorPosition; +@property (nonatomic, nullable, readonly) NSNumber *unreadIndicatorPosition; // If there are unseen messages in the thread, this is the timestamp // of the oldest unseen messaage. @@ -34,7 +34,9 @@ NS_ASSUME_NONNULL_BEGIN // repeatedly. The unread indicator should continue to show up until // it has been cleared, at which point hideUnreadMessagesIndicator is // YES in ensureDynamicInteractionsForThread:... -@property (nonatomic, nullable) NSNumber *firstUnseenInteractionTimestamp; +@property (nonatomic, nullable, readonly) NSNumber *firstUnseenInteractionTimestamp; + +@property (nonatomic, readonly) BOOL hasMoreUnseenMessages; - (void)clearUnreadIndicatorState; diff --git a/Signal/src/util/ThreadUtil.m b/Signal/src/util/ThreadUtil.m index e3ea27574..7dfb85018 100644 --- a/Signal/src/util/ThreadUtil.m +++ b/Signal/src/util/ThreadUtil.m @@ -18,12 +18,25 @@ NS_ASSUME_NONNULL_BEGIN +@interface ThreadDynamicInteractions () + +@property (nonatomic, nullable) NSNumber *unreadIndicatorPosition; + +@property (nonatomic, nullable) NSNumber *firstUnseenInteractionTimestamp; + +@property (nonatomic) BOOL hasMoreUnseenMessages; + +@end + +#pragma mark - + @implementation ThreadDynamicInteractions - (void)clearUnreadIndicatorState { self.unreadIndicatorPosition = nil; self.firstUnseenInteractionTimestamp = nil; + self.hasMoreUnseenMessages = NO; } @end @@ -200,7 +213,6 @@ NS_ASSUME_NONNULL_BEGIN // so that it can widen its "load window" to always show // the unread indicator. __block long visibleUnseenMessageCount = 0; - __block BOOL hasMoreUnseenMessages = NO; __block TSInteraction *interactionAfterUnreadIndicator = nil; NSUInteger missingUnseenSafetyNumberChangeCount = 0; if (result.firstUnseenInteractionTimestamp != nil) { @@ -243,13 +255,13 @@ NS_ASSUME_NONNULL_BEGIN // messages view, show the unread indicator at the top of the // displayed messages. *stop = YES; - hasMoreUnseenMessages = YES; + result.hasMoreUnseenMessages = YES; } }]; OWSAssert(interactionAfterUnreadIndicator); - if (hasMoreUnseenMessages) { + if (result.hasMoreUnseenMessages) { NSMutableSet *missingUnseenSafetyNumberChanges = [NSMutableSet set]; for (TSInvalidIdentityKeyErrorMessage *safetyNumberChange in blockingSafetyNumberChanges) { BOOL isUnseen = safetyNumberChange.timestampForSorting @@ -403,7 +415,7 @@ NS_ASSUME_NONNULL_BEGIN TSUnreadIndicatorInteraction *indicator = [[TSUnreadIndicatorInteraction alloc] initWithTimestamp:indicatorTimestamp thread:thread - hasMoreUnseenMessages:hasMoreUnseenMessages + hasMoreUnseenMessages:result.hasMoreUnseenMessages missingUnseenSafetyNumberChangeCount:missingUnseenSafetyNumberChangeCount]; [indicator saveWithTransaction:transaction];