From 227fd5280de4187b534b8a2b0a4abbe69f6e45e6 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 11 Oct 2017 15:23:53 -0400 Subject: [PATCH] Resize conversation view cells as necessary. // FREEBIE --- .../Cells/OWSAudioMessageView.m | 2 +- .../ConversationView/Cells/OWSMessageCell.m | 3 - .../ConversationInputTextView.m | 2 +- .../ConversationViewController.m | 109 +++++++++++------- .../ConversationView/ConversationViewItem.h | 3 + .../ConversationView/ConversationViewItem.m | 7 +- .../CountryCodeViewController.m | 2 +- .../ViewControllers/NewGroupViewController.m | 4 +- .../ViewControllers/ProfileViewController.m | 2 +- .../RegistrationViewController.m | 2 +- .../UpdateGroupViewController.m | 8 +- Signal/src/environment/NotificationsManager.m | 3 +- Signal/src/util/NSString+OWS.m | 2 +- SignalServiceKit/src/Contacts/Contact.m | 2 +- 14 files changed, 93 insertions(+), 58 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.m index 66eb013a2..11e0aa03e 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.m @@ -220,7 +220,7 @@ NS_ASSUME_NONNULL_BEGIN filename = [[self.attachmentStream filePath] lastPathComponent]; } NSString *topText = [[filename stringByDeletingPathExtension] - stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; if (topText.length < 1) { topText = [MIMETypeUtil fileExtensionForMIMEType:self.attachmentStream.contentType].uppercaseString; } diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index 8da9f8309..5623543f0 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -56,11 +56,8 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssert(!self.textLabel); - // [self setTranslatesAutoresizingMaskIntoConstraints:NO]; self.layoutMargins = UIEdgeInsetsZero; - self.contentView.backgroundColor = [UIColor whiteColor]; - self.payloadView = [UIView containerView]; [self.contentView addSubview:self.payloadView]; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationInputTextView.m b/Signal/src/ViewControllers/ConversationView/ConversationInputTextView.m index 0245baaf0..2d0fefa2c 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationInputTextView.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationInputTextView.m @@ -121,7 +121,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSString *)trimmedText { - return [self.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + return [self.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } // TODO: diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 183f6d1b7..b8699c9cb 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -2769,7 +2769,10 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { return; } - [self reloadViewItems]; + NSMutableSet *rowsThatChangedSize = [[self reloadViewItems] mutableCopy]; + for (NSNumber *row in rowsThatChangedSize) { + DDLogError(@"might reload: %@", row); + } BOOL wasAtBottom = [self isScrolledToBottom]; // We want sending messages to feel snappy. So, if the only @@ -2783,35 +2786,23 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { __block BOOL scrollToBottom = wasAtBottom; [self.collectionView performBatchUpdates:^{ - // TODO: We need to reload neighbords of changed cells. - void (^reloadNeighbors)(NSIndexPath *) = ^(NSIndexPath *indexPath) { - // if (indexPath.row > 0) { - // [self.collectionView reloadItemsAtIndexPaths:@[ [NSIndexPath indexPathForRow:indexPath.row - // - 1 - // inSection:0] ]]; - // } - // if (indexPath.row + 1 < (NSInteger) self.viewItems.count) { - // [self.collectionView reloadItemsAtIndexPaths:@[ [NSIndexPath indexPathForRow:indexPath.row - // + 1 - // inSection:0] ]]; - // } - }; - for (YapDatabaseViewRowChange *rowChange in messageRowChanges) { switch (rowChange.type) { case YapDatabaseViewChangeDelete: { - DDLogError(@".... YapDatabaseViewChangeDelete: %@", rowChange.collectionKey); + DDLogError( + @".... YapDatabaseViewChangeDelete: %@, %@", rowChange.collectionKey, rowChange.indexPath); [self.collectionView deleteItemsAtIndexPaths:@[ rowChange.indexPath ]]; - + [rowsThatChangedSize removeObject:@(rowChange.indexPath.row)]; YapCollectionKey *collectionKey = rowChange.collectionKey; OWSAssert(collectionKey.key.length > 0); - reloadNeighbors(rowChange.indexPath); break; } case YapDatabaseViewChangeInsert: { - DDLogError(@".... YapDatabaseViewChangeInsert: %@", rowChange.collectionKey); + DDLogError( + @".... YapDatabaseViewChangeInsert: %@, %@", rowChange.collectionKey, rowChange.newIndexPath); [self.collectionView insertItemsAtIndexPaths:@[ rowChange.newIndexPath ]]; + [rowsThatChangedSize removeObject:@(rowChange.newIndexPath.row)]; TSInteraction *interaction = [self interactionAtIndexPath:rowChange.newIndexPath]; if ([interaction isKindOfClass:[TSOutgoingMessage class]]) { @@ -2821,19 +2812,20 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { shouldAnimateScrollToBottom = NO; } } - reloadNeighbors(rowChange.newIndexPath); break; } case YapDatabaseViewChangeMove: { - DDLogError(@".... YapDatabaseViewChangeMove: %@", rowChange.collectionKey); + DDLogError(@".... YapDatabaseViewChangeMove: %@, %@, %@", + rowChange.collectionKey, + rowChange.indexPath, + rowChange.newIndexPath); [self.collectionView deleteItemsAtIndexPaths:@[ rowChange.indexPath ]]; [self.collectionView insertItemsAtIndexPaths:@[ rowChange.newIndexPath ]]; - reloadNeighbors(rowChange.indexPath); - reloadNeighbors(rowChange.newIndexPath); break; } case YapDatabaseViewChangeUpdate: { - DDLogError(@".... YapDatabaseViewChangeUpdate: %@", rowChange.collectionKey); + DDLogError( + @".... YapDatabaseViewChangeUpdate: %@, %@", rowChange.collectionKey, rowChange.indexPath); YapCollectionKey *collectionKey = rowChange.collectionKey; OWSAssert(collectionKey.key.length > 0); if (collectionKey.key) { @@ -2841,11 +2833,19 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { [self reloadViewItem:viewItem]; } [self.collectionView reloadItemsAtIndexPaths:@[ rowChange.indexPath ]]; - reloadNeighbors(rowChange.indexPath); + [rowsThatChangedSize removeObject:@(rowChange.indexPath.row)]; break; } } } + + // The changes performed above may affect the size of neighboring cells, + // as they may affect which cells show "date" headers or "status" footers. + NSMutableArray *rowsToReload = [NSMutableArray new]; + for (NSNumber *row in rowsThatChangedSize) { + [rowsToReload addObject:[NSIndexPath indexPathForRow:row.integerValue inSection:0]]; + } + [self.collectionView reloadItemsAtIndexPaths:rowsToReload]; } completion:^(BOOL success) { OWSAssert([NSThread isMainThread]); @@ -3595,7 +3595,7 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { return; } - text = [text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + text = [text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; if (text.length < 1) { return; @@ -3810,7 +3810,11 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { // This is a key method. It builds or rebuilds the list of // cell view models. -- (void)reloadViewItems +// +// Returns a list of the rows which may have changed size and +// need to be reloaded if we're doing an incremental update +// of the view. +- (NSSet *)reloadViewItems { NSMutableArray *viewItems = [NSMutableArray new]; NSMutableDictionary *viewItemMap = [NSMutableDictionary new]; @@ -3828,29 +3832,46 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { OWSAssert(interaction); ConversationViewItem *_Nullable viewItem = self.viewItemMap[interaction.uniqueId]; - if (!viewItem) { + if (viewItem) { + viewItem.lastRow = viewItem.row; + } else { viewItem = [[ConversationViewItem alloc] initWithTSInteraction:interaction]; } + viewItem.row = (NSInteger)row; [viewItems addObject:viewItem]; OWSAssert(!viewItemMap[interaction.uniqueId]); viewItemMap[interaction.uniqueId] = viewItem; } }]; + NSMutableSet *rowsThatChangedSize = [NSMutableSet new]; + // Update the "shouldShowDate" property of the view items. - int row = 0; - BOOL shouldShowDateOnNextViewItem = NO; + BOOL shouldShowDateOnNextViewItem = YES; uint64_t previousViewItemTimestamp = 0; for (ConversationViewItem *viewItem in viewItems) { - if (row == 0) { - viewItem.shouldShowDate = YES; - shouldShowDateOnNextViewItem = NO; - } else if (viewItem.interaction.interactionType == OWSInteractionType_UnreadIndicator - || viewItem.interaction.interactionType == OWSInteractionType_Offer) { - viewItem.shouldShowDate = NO; + BOOL canShowDate = NO; + switch (viewItem.interaction.interactionType) { + case OWSInteractionType_Unknown: + case OWSInteractionType_UnreadIndicator: + case OWSInteractionType_Offer: + canShowDate = NO; + break; + case OWSInteractionType_IncomingMessage: + case OWSInteractionType_OutgoingMessage: + case OWSInteractionType_Error: + case OWSInteractionType_Info: + case OWSInteractionType_Call: + canShowDate = YES; + break; + } + + BOOL shouldShowDate = NO; + if (!canShowDate) { + shouldShowDate = NO; shouldShowDateOnNextViewItem = YES; } else if (shouldShowDateOnNextViewItem) { - viewItem.shouldShowDate = YES; + shouldShowDate = YES; shouldShowDateOnNextViewItem = NO; } else { uint64_t viewItemTimestamp = viewItem.interaction.timestampForSorting; @@ -3859,16 +3880,26 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { uint64_t timeDifferenceMs = viewItemTimestamp - previousViewItemTimestamp; static const uint64_t kShowTimeIntervalMs = 5 * kMinuteInMs; if (timeDifferenceMs > kShowTimeIntervalMs) { - viewItem.shouldShowDate = YES; + shouldShowDate = YES; } shouldShowDateOnNextViewItem = NO; } + if (viewItem.shouldShowDate != shouldShowDate) { + // If this is an existing view item and it has changed size, + // note that so that we can reload this cell while doing + // incremental updates. + if (viewItem.lastRow != NSNotFound) { + [rowsThatChangedSize addObject:@(viewItem.lastRow)]; + } + } + viewItem.shouldShowDate = shouldShowDate; previousViewItemTimestamp = viewItem.interaction.timestampForSorting; - row++; } self.viewItems = viewItems; self.viewItemMap = viewItemMap; + + return [rowsThatChangedSize copy]; } // Whenever an interaction is modified, we need to reload it from the DB diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h index 9f2c45aca..0001ac089 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h @@ -41,6 +41,9 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); @property (nonatomic) BOOL shouldShowDate; +@property (nonatomic) NSInteger row; +@property (nonatomic) NSInteger lastRow; + //@property (nonatomic, weak) ConversationViewCell *lastCell; - (instancetype)init NS_UNAVAILABLE; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index ea64fb5b3..970231e95 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -70,6 +70,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) } _interaction = interaction; + self.row = NSNotFound; + self.lastRow = NSNotFound; return self; } @@ -265,13 +267,14 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) if (!displayableText) { // Only show up to 2kb of text. const NSUInteger kMaxTextDisplayLength = 2 * 1024; + text = [text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; displayableText = [[DisplayableTextFilter new] displayableText:text]; if (displayableText.length > kMaxTextDisplayLength) { // Trim whitespace before _AND_ after slicing the snipper from the string. NSString *snippet = - [[[displayableText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] + [[[displayableText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] substringWithRange:NSMakeRange(0, kMaxTextDisplayLength)] - stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; displayableText = [NSString stringWithFormat:NSLocalizedString(@"OVERSIZE_TEXT_DISPLAY_FORMAT", @"A display format for oversize text messages."), snippet]; diff --git a/Signal/src/ViewControllers/CountryCodeViewController.m b/Signal/src/ViewControllers/CountryCodeViewController.m index 60196297a..94771136b 100644 --- a/Signal/src/ViewControllers/CountryCodeViewController.m +++ b/Signal/src/ViewControllers/CountryCodeViewController.m @@ -143,7 +143,7 @@ - (void)searchTextDidChange { NSString *searchText = - [self.searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + [self.searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; self.countryCodes = [PhoneNumberUtil countryCodesForSearchTerm:searchText]; diff --git a/Signal/src/ViewControllers/NewGroupViewController.m b/Signal/src/ViewControllers/NewGroupViewController.m index dc1fae3ff..7e8d9babf 100644 --- a/Signal/src/ViewControllers/NewGroupViewController.m +++ b/Signal/src/ViewControllers/NewGroupViewController.m @@ -515,8 +515,8 @@ const NSUInteger kNewGroupViewControllerAvatarWidth = 68; - (TSGroupModel *)makeGroup { - NSString *groupName = - [self.groupNameTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + NSString *groupName = [self.groupNameTextField.text + stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; NSMutableArray *recipientIds = [self.memberRecipientIds.allObjects mutableCopy]; [recipientIds addObject:[self.contactsViewHelper localNumber]]; NSData *groupId = [SecurityUtils generateRandomBytes:16]; diff --git a/Signal/src/ViewControllers/ProfileViewController.m b/Signal/src/ViewControllers/ProfileViewController.m index ac001fe53..4804e9625 100644 --- a/Signal/src/ViewControllers/ProfileViewController.m +++ b/Signal/src/ViewControllers/ProfileViewController.m @@ -410,7 +410,7 @@ NSString *const kProfileView_LastPresentedDate = @"kProfileView_LastPresentedDat - (NSString *)normalizedProfileName { - return [self.nameTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + return [self.nameTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } - (void)updateProfileCompleted diff --git a/Signal/src/ViewControllers/RegistrationViewController.m b/Signal/src/ViewControllers/RegistrationViewController.m index 2aa3e7558..e86d64079 100644 --- a/Signal/src/ViewControllers/RegistrationViewController.m +++ b/Signal/src/ViewControllers/RegistrationViewController.m @@ -281,7 +281,7 @@ NSString *const kKeychainKey_LastRegisteredPhoneNumber = @"kKeychainKey_LastRegi - (void)sendCodeAction { NSString *phoneNumberText = - [_phoneNumberTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + [_phoneNumberTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; if (phoneNumberText.length < 1) { [OWSAlerts showAlertWithTitle:NSLocalizedString(@"REGISTRATION_VIEW_NO_PHONE_NUMBER_ALERT_TITLE", diff --git a/Signal/src/ViewControllers/UpdateGroupViewController.m b/Signal/src/ViewControllers/UpdateGroupViewController.m index 555c9c2b5..eaced2e56 100644 --- a/Signal/src/ViewControllers/UpdateGroupViewController.m +++ b/Signal/src/ViewControllers/UpdateGroupViewController.m @@ -196,8 +196,8 @@ NS_ASSUME_NONNULL_BEGIN UITextField *groupNameTextField = [UITextField new]; _groupNameTextField = groupNameTextField; - self.groupNameTextField.text = - [self.thread.groupModel.groupName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + self.groupNameTextField.text = [self.thread.groupModel.groupName + stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; groupNameTextField.textColor = [UIColor blackColor]; groupNameTextField.font = [UIFont ows_dynamicTypeTitle2Font]; groupNameTextField.placeholder @@ -373,8 +373,8 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssert(self.conversationSettingsViewDelegate); - NSString *groupName = - [self.groupNameTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + NSString *groupName = [self.groupNameTextField.text + stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; TSGroupModel *groupModel = [[TSGroupModel alloc] initWithTitle:groupName memberIds:[self.memberRecipientIds.allObjects mutableCopy] image:self.groupAvatar diff --git a/Signal/src/environment/NotificationsManager.m b/Signal/src/environment/NotificationsManager.m index f6ab2a7a8..30a9ad50e 100644 --- a/Signal/src/environment/NotificationsManager.m +++ b/Signal/src/environment/NotificationsManager.m @@ -272,7 +272,8 @@ NSString *const kNotificationsManagerNewMesssageSoundName = @"NewMessage.aifc"; BOOL shouldPlaySound = [self shouldPlaySoundForNotification]; NSString *senderName = [contactsManager displayNameForPhoneIdentifier:message.authorId]; - NSString *groupName = [thread.name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + NSString *groupName = + [thread.name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; if (groupName.length < 1) { groupName = [MessageStrings newGroupDefaultTitle]; } diff --git a/Signal/src/util/NSString+OWS.m b/Signal/src/util/NSString+OWS.m index 45ae7db25..c14873f98 100644 --- a/Signal/src/util/NSString+OWS.m +++ b/Signal/src/util/NSString+OWS.m @@ -11,7 +11,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSString *)ows_stripped { - return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } - (NSString *)rtlSafeAppend:(NSString *)string referenceView:(UIView *)referenceView diff --git a/SignalServiceKit/src/Contacts/Contact.m b/SignalServiceKit/src/Contacts/Contact.m index 532b544c6..6010c0ebd 100644 --- a/SignalServiceKit/src/Contacts/Contact.m +++ b/SignalServiceKit/src/Contacts/Contact.m @@ -136,7 +136,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSString *)trimName:(NSString *)name { - return [name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + return [name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } + (NSString *)uniqueIdFromABRecordId:(ABRecordID)recordId