diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 40a2bfb84..9bbb365fa 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -465,6 +465,13 @@ typedef enum : NSUInteger { return; } + // Cells' appearance can depend on adjacent cells in both directions. + [self.messageMappings setCellDrawingDependencyOffsets:[NSSet setWithArray:@[ + @(-1), + @(+1), + ]] + forGroup:self.thread.uniqueId]; + // We need to impose the range restrictions on the mappings immediately to avoid // doing a great deal of unnecessary work and causing a perf hotspot. [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { @@ -3303,8 +3310,16 @@ typedef enum : NSUInteger { case YapDatabaseViewChangeUpdate: { YapCollectionKey *collectionKey = rowChange.collectionKey; if (collectionKey.key) { - ConversationViewItem *viewItem = self.viewItemCache[collectionKey.key]; - [self reloadInteractionForViewItem:viewItem]; + ConversationViewItem *_Nullable viewItem = self.viewItemCache[collectionKey.key]; + if (viewItem) { + [self reloadInteractionForViewItem:viewItem]; + } else { + hasMalformedRowChange = YES; + } + } else if (rowChange.indexPath && rowChange.originalIndex < self.viewItems.count) { + // Do nothing, this is a pseudo-update generated due to + // setCellDrawingDependencyOffsets. + OWSAssert(rowChange.changes == YapDatabaseViewChangedDependency); } else { hasMalformedRowChange = YES; } @@ -3332,7 +3347,8 @@ typedef enum : NSUInteger { if (hasMalformedRowChange) { // These errors seems to be very rare; they can only be reproduced // using the more extreme actions in the debug UI. - DDLogError(@"%@ hasMalformedRowChange", self.logTag); + OWSProdLogAndFail(@"%@ hasMalformedRowChange", self.logTag); + [self reloadViewItems]; [self.collectionView reloadData]; [self updateLastVisibleTimestamp]; [self cleanUpUnreadIndicatorIfNecessary]; @@ -3340,7 +3356,7 @@ typedef enum : NSUInteger { } NSUInteger oldViewItemCount = self.viewItems.count; - NSMutableSet *rowsThatChangedSize = [[self reloadViewItems] mutableCopy]; + [self reloadViewItems]; BOOL wasAtBottom = [self isScrolledToBottom]; // We want sending messages to feel snappy. So, if the only @@ -3372,8 +3388,6 @@ typedef enum : NSUInteger { rowChange.newIndexPath, rowChange.finalIndex); [self.collectionView insertItemsAtIndexPaths:@[ rowChange.newIndexPath ]]; - // We don't want to reload a row that we just inserted. - [rowsThatChangedSize removeObject:@(rowChange.originalIndex)]; ConversationViewItem *_Nullable viewItem = [self viewItemForIndex:(NSInteger)rowChange.finalIndex]; if ([viewItem.interaction isKindOfClass:[TSOutgoingMessage class]]) { @@ -3392,8 +3406,6 @@ typedef enum : NSUInteger { rowChange.newIndexPath, rowChange.finalIndex); [self.collectionView moveItemAtIndexPath:rowChange.indexPath toIndexPath:rowChange.newIndexPath]; - // We don't want to reload a row that we just moved. - [rowsThatChangedSize removeObject:@(rowChange.originalIndex)]; break; } case YapDatabaseViewChangeUpdate: { @@ -3402,23 +3414,10 @@ typedef enum : NSUInteger { rowChange.indexPath, rowChange.finalIndex); [self.collectionView reloadItemsAtIndexPaths:@[ rowChange.indexPath ]]; - // We don't want to reload a row that we've already reloaded. - [rowsThatChangedSize removeObject:@(rowChange.originalIndex)]; 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) { - DDLogVerbose(@"rowsToReload: %@", row); - [rowsToReload addObject:[NSIndexPath indexPathForRow:row.integerValue inSection:0]]; - } - if (rowsToReload.count > 0) { - [self.collectionView reloadItemsAtIndexPaths:rowsToReload]; - } }; DDLogVerbose(@"self.viewItems.count: %zd -> %zd", oldViewItemCount, self.viewItems.count); @@ -4775,11 +4774,7 @@ typedef enum : NSUInteger { // This is a key method. It builds or rebuilds the list of // cell view models. -// -// 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 +- (void)reloadViewItems { NSMutableArray *viewItems = [NSMutableArray new]; NSMutableDictionary *viewItemCache = [NSMutableDictionary new]; @@ -4809,23 +4804,18 @@ typedef enum : NSUInteger { } ConversationViewItem *_Nullable viewItem = self.viewItemCache[interaction.uniqueId]; - if (viewItem) { - viewItem.previousRow = viewItem.row; - } else { + if (!viewItem) { viewItem = [[ConversationViewItem alloc] initWithInteraction:interaction isGroupThread:isGroupThread transaction:transaction conversationStyle:self.conversationStyle]; } - viewItem.row = (NSInteger)row; [viewItems addObject:viewItem]; OWSAssert(!viewItemCache[interaction.uniqueId]); viewItemCache[interaction.uniqueId] = viewItem; } }]; - NSMutableSet *rowsThatChangedSize = [NSMutableSet new]; - // Update the "shouldShowDate" property of the view items. BOOL shouldShowDateOnNextViewItem = YES; uint64_t previousViewItemTimestamp = 0; @@ -4865,12 +4855,6 @@ typedef enum : NSUInteger { shouldShowDateOnNextViewItem = NO; } - // 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.shouldShowDate != shouldShowDate && viewItem.previousRow != NSNotFound) { - [rowsThatChangedSize addObject:@(viewItem.previousRow)]; - } viewItem.shouldShowDate = shouldShowDate; previousViewItemTimestamp = viewItem.interaction.timestampForSorting; @@ -4910,20 +4894,12 @@ typedef enum : NSUInteger { } lastInteractionType = interactionType; - // 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.shouldHideRecipientStatus != shouldHideRecipientStatus && viewItem.previousRow != NSNotFound) { - [rowsThatChangedSize addObject:@(viewItem.previousRow)]; - } viewItem.shouldHideRecipientStatus = shouldHideRecipientStatus; viewItem.shouldHideAvatar = shouldHideAvatar; } self.viewItems = viewItems; self.viewItemCache = viewItemCache; - - 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 4c6694124..a29c22fc5 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h @@ -60,14 +60,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); // Used to suppress "group sender" avatars. @property (nonatomic) BOOL shouldHideAvatar; -@property (nonatomic) NSInteger row; -// During updates, we sometimes need the previous row index -// (before this update) of this item. -// -// If NSNotFound, this view item was just created in the -// previous update. -@property (nonatomic) NSInteger previousRow; - @property (nonatomic, readonly) ConversationStyle *conversationStyle; - (instancetype)init NS_UNAVAILABLE; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index adaaa8ecf..e5a67aca4 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -92,8 +92,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) _interaction = interaction; _isGroupThread = isGroupThread; _conversationStyle = conversationStyle; - self.row = NSNotFound; - self.previousRow = NSNotFound; [self ensureViewState:transaction]; @@ -271,8 +269,21 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) return 0.f; } + // "Bubble Collapse". Adjacent messages with the same author should be close together. + if (self.interaction.interactionType == OWSInteractionType_IncomingMessage + && previousLayoutItem.interaction.interactionType == OWSInteractionType_IncomingMessage) { + TSIncomingMessage *incomingMessage = (TSIncomingMessage *)self.interaction; + TSIncomingMessage *previousIncomingMessage = (TSIncomingMessage *)previousLayoutItem.interaction; + if ([incomingMessage.authorId isEqualToString:previousIncomingMessage.authorId]) { + return 2.f; + } + } else if (self.interaction.interactionType == OWSInteractionType_OutgoingMessage + && previousLayoutItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) { + return 2.f; + } + // TODO: - return 4.f; + return 10.f; } - (ConversationViewCell *)dequeueCellForCollectionView:(UICollectionView *)collectionView diff --git a/Signal/src/ViewControllers/HomeView/HomeViewCell.m b/Signal/src/ViewControllers/HomeView/HomeViewCell.m index fa9feb95a..90228c83c 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewCell.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewCell.m @@ -233,7 +233,6 @@ NS_ASSUME_NONNULL_BEGIN relation:NSLayoutRelationGreaterThanOrEqual], [self.unreadBadge autoSetDimension:ALDimensionHeight toSize:unreadBadgeHeight], ]]; - }]; const CGFloat kMinVMargin = 5; diff --git a/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m b/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m index d30251745..a187f8275 100644 --- a/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m +++ b/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m @@ -526,7 +526,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_ NSError *error; BOOL wasScheduled = [self.websocket sendDataNoCopy:messageData error:&error]; if (!wasScheduled || error) { - OWSProdLogAndFail(@"%@ could not serialize request JSON: %@", self.logTag, error); + OWSProdLogAndFail(@"%@ could not send socket request: %@", self.logTag, error); [socketMessage didFailBeforeSending]; return; }