From 4e1dda275dcc399f80aeac32065453345a598f80 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 16 May 2017 13:46:57 -0400 Subject: [PATCH 1/4] Revise scrolling behavior of messages view. // FREEBIE --- Signal/src/UIView+OWS.m | 9 +- .../DebugUITableViewController.m | 10 +- .../ViewControllers/MessagesViewController.m | 93 +++++++++++++++---- 3 files changed, 88 insertions(+), 24 deletions(-) diff --git a/Signal/src/UIView+OWS.m b/Signal/src/UIView+OWS.m index 9531b793c..64143fc6e 100644 --- a/Signal/src/UIView+OWS.m +++ b/Signal/src/UIView+OWS.m @@ -167,11 +167,10 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value) { OWSAssert(self.superview); - self.frame = CGRectMake( - round(self.superview.bounds.origin.x + (self.superview.bounds.size.width - self.frame.size.width) * 0.5f), - round(self.superview.bounds.origin.y + (self.superview.bounds.size.height - self.frame.size.height) * 0.5f), - self.frame.size.width, - self.frame.size.height); + self.frame = CGRectMake(round(self.superview.left + (self.superview.width - self.width) * 0.5f), + round(self.superview.top + (self.superview.height - self.height) * 0.5f), + self.width, + self.height); } #pragma mark - Debugging diff --git a/Signal/src/ViewControllers/DebugUITableViewController.m b/Signal/src/ViewControllers/DebugUITableViewController.m index b890f92d8..c284b69cf 100644 --- a/Signal/src/ViewControllers/DebugUITableViewController.m +++ b/Signal/src/ViewControllers/DebugUITableViewController.m @@ -1302,9 +1302,13 @@ NS_ASSUME_NONNULL_BEGIN if (counter < 1) { return; } - [ThreadUtil sendMessageWithText:[@(counter) description] - inThread:thread - messageSender:messageSender]; + [ThreadUtil + sendMessageWithText:[[@(counter) description] + stringByAppendingString:@" Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + @"Suspendisse rutrum, nulla vitae pretium hendrerit, tellus " + @"turpis pharetra libero, vitae sodales tortor ante vel sem."] + inThread:thread + messageSender:messageSender]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) 1.f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ [self sendTextMessage:counter - 1 thread:thread]; diff --git a/Signal/src/ViewControllers/MessagesViewController.m b/Signal/src/ViewControllers/MessagesViewController.m index f2c4d846b..b2f720b4b 100644 --- a/Signal/src/ViewControllers/MessagesViewController.m +++ b/Signal/src/ViewControllers/MessagesViewController.m @@ -98,6 +98,8 @@ typedef enum : NSUInteger { - (void)didPasteAttachment:(SignalAttachment * _Nullable)attachment; +- (void)textViewDidChangeSize; + @end #pragma mark - @@ -146,6 +148,28 @@ typedef enum : NSUInteger { [super paste:sender]; } +- (void)setFrame:(CGRect)frame +{ + BOOL didChangeSize = !CGSizeEqualToSize(frame.size, self.frame.size); + + [super setFrame:frame]; + + if (didChangeSize) { + [self.textViewPasteDelegate textViewDidChangeSize]; + } +} + +- (void)setBounds:(CGRect)bounds +{ + BOOL didChangeSize = !CGSizeEqualToSize(bounds.size, self.bounds.size); + + [super setBounds:bounds]; + + if (didChangeSize) { + [self.textViewPasteDelegate textViewDidChangeSize]; + } +} + @end #pragma mark - @@ -782,6 +806,7 @@ typedef enum : NSUInteger { self.senderId = ME_MESSAGE_IDENTIFIER; self.senderDisplayName = ME_MESSAGE_IDENTIFIER; + self.automaticallyScrollsToMostRecentMessage = NO; [self initializeToolbars]; @@ -935,14 +960,6 @@ typedef enum : NSUInteger { [self setBarButtonItemsForDisappearingMessagesConfiguration:configuration]; [self setNavigationTitle]; - NSInteger numberOfMessages = (NSInteger)[self.messageMappings numberOfItemsInGroup:self.thread.uniqueId]; - if (numberOfMessages > 0) { - NSIndexPath *lastCellIndexPath = [NSIndexPath indexPathForRow:numberOfMessages - 1 inSection:0]; - [self.collectionView scrollToItemAtIndexPath:lastCellIndexPath - atScrollPosition:UICollectionViewScrollPositionBottom - animated:NO]; - } - // Other views might change these custom menu items, so we // need to set them every time we enter this view. SEL saveSelector = NSSelectorFromString(@"save:"); @@ -961,6 +978,35 @@ typedef enum : NSUInteger { [self resetContentAndLayout]; [((OWSMessagesToolbarContentView *)self.inputToolbar.contentView)ensureSubviews]; + + [self scrollToDefaultPosition]; + [self.collectionView.collectionViewLayout + invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; +} + +- (NSIndexPath *_Nullable)indexPathOfUnreadMessagesIndicator +{ + int numberOfMessages = (int)[self.messageMappings numberOfItemsInGroup:self.thread.uniqueId]; + for (int i = 0; i < numberOfMessages; i++) { + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0]; + id message = [self messageAtIndexPath:indexPath]; + if (message.messageType == TSUnreadIndicatorAdapter) { + return indexPath; + } + } + return nil; +} + +- (void)scrollToDefaultPosition +{ + NSIndexPath *_Nullable indexPath = [self indexPathOfUnreadMessagesIndicator]; + if (indexPath) { + [self.collectionView scrollToItemAtIndexPath:indexPath + atScrollPosition:UICollectionViewScrollPositionTop + animated:NO]; + } else { + [self scrollToBottomAnimated:NO]; + } } - (void)resetContentAndLayout @@ -3022,11 +3068,7 @@ typedef enum : NSUInteger { return; } - const CGFloat kIsAtBottomTolerancePts = 5; - BOOL wasAtBottom = (self.collectionView.contentOffset.y + - self.collectionView.bounds.size.height + - kIsAtBottomTolerancePts >= - self.collectionView.contentSize.height); + BOOL wasAtBottom = [self isScrolledToBottom]; // We want sending messages to feel snappy. So, if the only // update is a new outgoing message AND we're already scrolled to // the bottom of the conversation, skip the scroll animation. @@ -3052,11 +3094,11 @@ typedef enum : NSUInteger { } case YapDatabaseViewChangeInsert: { [self.collectionView insertItemsAtIndexPaths:@[ rowChange.newIndexPath ]]; - scrollToBottom = YES; TSInteraction *interaction = [self interactionAtIndexPath:rowChange.newIndexPath]; - if (![interaction isKindOfClass:[TSOutgoingMessage class]]) { - shouldAnimateScrollToBottom = YES; + if ([interaction isKindOfClass:[TSOutgoingMessage class]]) { + scrollToBottom = YES; + shouldAnimateScrollToBottom = NO; } break; } @@ -3088,6 +3130,13 @@ typedef enum : NSUInteger { }]; } +- (BOOL)isScrolledToBottom +{ + const CGFloat kIsAtBottomTolerancePts = 5; + return (self.collectionView.contentOffset.y + self.collectionView.bounds.size.height + kIsAtBottomTolerancePts + >= self.collectionView.contentSize.height); +} + #pragma mark - UICollectionView DataSource - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { @@ -3608,6 +3657,18 @@ typedef enum : NSUInteger { completion:nil]; } +- (void)textViewDidChangeSize +{ + OWSAssert([NSThread isMainThread]); + + BOOL wasAtBottom = [self isScrolledToBottom]; + if (wasAtBottom) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self scrollToBottomAnimated:NO]; + }); + } +} + #pragma mark - OWSMessagesToolbarContentDelegate - (void)voiceMemoGestureDidStart From c639926f2c28350fb496fd3d377042ceccdfdabb Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 16 May 2017 14:04:44 -0400 Subject: [PATCH 2/4] Revise scrolling behavior of messages view. // FREEBIE --- .../ViewControllers/MessagesViewController.m | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/Signal/src/ViewControllers/MessagesViewController.m b/Signal/src/ViewControllers/MessagesViewController.m index b2f720b4b..563919931 100644 --- a/Signal/src/ViewControllers/MessagesViewController.m +++ b/Signal/src/ViewControllers/MessagesViewController.m @@ -150,22 +150,24 @@ typedef enum : NSUInteger { - (void)setFrame:(CGRect)frame { + BOOL isNonEmpty = (self.width > 0.f && self.height > 0.f); BOOL didChangeSize = !CGSizeEqualToSize(frame.size, self.frame.size); [super setFrame:frame]; - if (didChangeSize) { + if (didChangeSize && isNonEmpty) { [self.textViewPasteDelegate textViewDidChangeSize]; } } - (void)setBounds:(CGRect)bounds { + BOOL isNonEmpty = (self.width > 0.f && self.height > 0.f); BOOL didChangeSize = !CGSizeEqualToSize(bounds.size, self.bounds.size); [super setBounds:bounds]; - if (didChangeSize) { + if (didChangeSize && isNonEmpty) { [self.textViewPasteDelegate textViewDidChangeSize]; } } @@ -635,6 +637,7 @@ typedef enum : NSUInteger { @property (nonatomic) NSCache *messageAdapterCache; @property (nonatomic) BOOL userHasScrolled; @property (nonatomic) NSDate *lastMessageSentDate; +@property (nonatomic) NSTimer *scrollToBottomTimer; @end @@ -999,6 +1002,9 @@ typedef enum : NSUInteger { - (void)scrollToDefaultPosition { + [self.scrollToBottomTimer invalidate]; + self.scrollToBottomTimer = nil; + NSIndexPath *_Nullable indexPath = [self indexPathOfUnreadMessagesIndicator]; if (indexPath) { [self.collectionView scrollToItemAtIndexPath:indexPath @@ -3067,7 +3073,7 @@ typedef enum : NSUInteger { if ([sectionChanges count] == 0 & [messageRowChanges count] == 0) { return; } - + BOOL wasAtBottom = [self isScrolledToBottom]; // We want sending messages to feel snappy. So, if the only // update is a new outgoing message AND we're already scrolled to @@ -3663,12 +3669,25 @@ typedef enum : NSUInteger { BOOL wasAtBottom = [self isScrolledToBottom]; if (wasAtBottom) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self scrollToBottomAnimated:NO]; - }); + [self.scrollToBottomTimer invalidate]; + self.scrollToBottomTimer = [NSTimer weakScheduledTimerWithTimeInterval:0.001f + target:self + selector:@selector(scrollToBottomImmediately) + userInfo:nil + repeats:NO]; } } +- (void)scrollToBottomImmediately +{ + OWSAssert([NSThread isMainThread]); + + [self.scrollToBottomTimer invalidate]; + self.scrollToBottomTimer = nil; + + [self scrollToBottomAnimated:NO]; +} + #pragma mark - OWSMessagesToolbarContentDelegate - (void)voiceMemoGestureDidStart From 14ebc58d522c751a395913069a814b792140865d Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 16 May 2017 15:52:19 -0400 Subject: [PATCH 3/4] Revise scrolling behavior of messages view. // FREEBIE --- .../ViewControllers/MessagesViewController.m | 45 ++++++++++++++----- Signal/src/util/ThreadUtil.h | 1 + Signal/src/util/ThreadUtil.m | 24 ++++++++++ 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/Signal/src/ViewControllers/MessagesViewController.m b/Signal/src/ViewControllers/MessagesViewController.m index 563919931..f18e18167 100644 --- a/Signal/src/ViewControllers/MessagesViewController.m +++ b/Signal/src/ViewControllers/MessagesViewController.m @@ -42,6 +42,7 @@ #import "TSIncomingMessage.h" #import "TSInfoMessage.h" #import "TSInvalidIdentityKeyErrorMessage.h" +#import "TSUnreadIndicatorInteraction.h" #import "ThreadUtil.h" #import "UIFont+OWS.h" #import "UIUtil.h" @@ -637,7 +638,7 @@ typedef enum : NSUInteger { @property (nonatomic) NSCache *messageAdapterCache; @property (nonatomic) BOOL userHasScrolled; @property (nonatomic) NSDate *lastMessageSentDate; -@property (nonatomic) NSTimer *scrollToBottomTimer; +@property (nonatomic) NSTimer *scrollLaterTimer; @end @@ -982,9 +983,20 @@ typedef enum : NSUInteger { [((OWSMessagesToolbarContentView *)self.inputToolbar.contentView)ensureSubviews]; - [self scrollToDefaultPosition]; [self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; + + [self.scrollLaterTimer invalidate]; + self.scrollLaterTimer = [NSTimer weakScheduledTimerWithTimeInterval:0.001f + target:self + selector:@selector(scrollToDefaultPosition) + userInfo:nil + repeats:NO]; +} + +- (void)clearUnreadMessagesIndicator +{ + [ThreadUtil clearUnreadMessagesIndicator:self.thread storageManager:self.storageManager]; } - (NSIndexPath *_Nullable)indexPathOfUnreadMessagesIndicator @@ -1002,8 +1014,8 @@ typedef enum : NSUInteger { - (void)scrollToDefaultPosition { - [self.scrollToBottomTimer invalidate]; - self.scrollToBottomTimer = nil; + [self.scrollLaterTimer invalidate]; + self.scrollLaterTimer = nil; NSIndexPath *_Nullable indexPath = [self indexPathOfUnreadMessagesIndicator]; if (indexPath) { @@ -1608,6 +1620,8 @@ typedef enum : NSUInteger { [ThreadUtil sendMessageWithText:text inThread:self.thread messageSender:self.messageSender]; } self.lastMessageSentDate = [NSDate new]; + [self clearUnreadMessagesIndicator]; + if (updateKeyboardState) { [self toggleDefaultKeyboard]; @@ -2366,6 +2380,8 @@ typedef enum : NSUInteger { self.page++; } + [self.scrollLaterTimer invalidate]; + self.scrollLaterTimer = nil; NSInteger item = (NSInteger)[self scrollToItem]; [self updateRangeOptionsForPage:self.page]; @@ -2418,6 +2434,8 @@ typedef enum : NSUInteger { invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; [self.collectionView reloadData]; + [self.scrollLaterTimer invalidate]; + self.scrollLaterTimer = nil; [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:offset inSection:0] atScrollPosition:UICollectionViewScrollPositionTop animated:NO]; @@ -2951,6 +2969,7 @@ typedef enum : NSUInteger { [attachment mimeType]); [ThreadUtil sendMessageWithAttachment:attachment inThread:self.thread messageSender:self.messageSender]; self.lastMessageSentDate = [NSDate new]; + [self clearUnreadMessagesIndicator]; } - (NSURL *)videoTempFolder { @@ -3131,6 +3150,8 @@ typedef enum : NSUInteger { [self.collectionView reloadData]; } if (scrollToBottom) { + [self.scrollLaterTimer invalidate]; + self.scrollLaterTimer = nil; [self scrollToBottomAnimated:shouldAnimateScrollToBottom]; } }]; @@ -3669,12 +3690,12 @@ typedef enum : NSUInteger { BOOL wasAtBottom = [self isScrolledToBottom]; if (wasAtBottom) { - [self.scrollToBottomTimer invalidate]; - self.scrollToBottomTimer = [NSTimer weakScheduledTimerWithTimeInterval:0.001f - target:self - selector:@selector(scrollToBottomImmediately) - userInfo:nil - repeats:NO]; + [self.scrollLaterTimer invalidate]; + self.scrollLaterTimer = [NSTimer weakScheduledTimerWithTimeInterval:0.001f + target:self + selector:@selector(scrollToBottomImmediately) + userInfo:nil + repeats:NO]; } } @@ -3682,8 +3703,8 @@ typedef enum : NSUInteger { { OWSAssert([NSThread isMainThread]); - [self.scrollToBottomTimer invalidate]; - self.scrollToBottomTimer = nil; + [self.scrollLaterTimer invalidate]; + self.scrollLaterTimer = nil; [self scrollToBottomAnimated:NO]; } diff --git a/Signal/src/util/ThreadUtil.h b/Signal/src/util/ThreadUtil.h index 7af2cbef1..cb1fcceaa 100644 --- a/Signal/src/util/ThreadUtil.h +++ b/Signal/src/util/ThreadUtil.h @@ -28,6 +28,7 @@ NS_ASSUME_NONNULL_BEGIN blockingManager:(OWSBlockingManager *)blockingManager; + (void)createUnreadMessagesIndicatorIfNecessary:(TSThread *)thread storageManager:(TSStorageManager *)storageManager; ++ (void)clearUnreadMessagesIndicator:(TSThread *)thread storageManager:(TSStorageManager *)storageManager; @end diff --git a/Signal/src/util/ThreadUtil.m b/Signal/src/util/ThreadUtil.m index 744382c30..09d246b8e 100644 --- a/Signal/src/util/ThreadUtil.m +++ b/Signal/src/util/ThreadUtil.m @@ -230,6 +230,30 @@ NS_ASSUME_NONNULL_BEGIN }]; } ++ (void)clearUnreadMessagesIndicator:(TSThread *)thread storageManager:(TSStorageManager *)storageManager +{ + OWSAssert(thread); + OWSAssert(storageManager); + + [storageManager.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + + NSMutableArray *indicators = [NSMutableArray new]; + [[transaction ext:TSMessageDatabaseViewExtensionName] + enumerateRowsInGroup:thread.uniqueId + usingBlock:^( + NSString *collection, NSString *key, id object, id metadata, NSUInteger index, BOOL *stop) { + + if ([object isKindOfClass:[TSUnreadIndicatorInteraction class]]) { + [indicators addObject:object]; + } + }]; + + for (TSUnreadIndicatorInteraction *indicator in indicators) { + [indicator removeWithTransaction:transaction]; + } + }]; +} + #pragma mark - Logging + (NSString *)tag From ac0c6e21de52ca2879fac61d0f10b2b95d6747de Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 19 May 2017 18:28:40 -0400 Subject: [PATCH 4/4] Respond to CR. // FREEBIE --- Signal/src/ViewControllers/MessagesViewController.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Signal/src/ViewControllers/MessagesViewController.m b/Signal/src/ViewControllers/MessagesViewController.m index f18e18167..f3a310477 100644 --- a/Signal/src/ViewControllers/MessagesViewController.m +++ b/Signal/src/ViewControllers/MessagesViewController.m @@ -987,6 +987,7 @@ typedef enum : NSUInteger { invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; [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(scrollToDefaultPosition) @@ -3691,6 +3692,7 @@ typedef enum : NSUInteger { BOOL wasAtBottom = [self isScrolledToBottom]; if (wasAtBottom) { [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(scrollToBottomImmediately)