diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 5486c4dbb..c767c6dd3 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -26,6 +26,8 @@ F0614A131EDEE65000F5DC9A /* cancel.png in Resources */ = {isa = PBXBuildFile; fileRef = F0614A101EDEE65000F5DC9A /* cancel.png */; }; F0614A141EDEE65000F5DC9A /* cancel@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0614A111EDEE65000F5DC9A /* cancel@2x.png */; }; F0614A151EDEE65000F5DC9A /* cancel@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0614A121EDEE65000F5DC9A /* cancel@3x.png */; }; + F06CDD691EF01E3900870B75 /* RoomEmptyBubbleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = F06CDD671EF01E3900870B75 /* RoomEmptyBubbleCell.m */; }; + F06CDD6A1EF01E3900870B75 /* RoomEmptyBubbleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F06CDD681EF01E3900870B75 /* RoomEmptyBubbleCell.xib */; }; F075BED61EBB169C00A7B68A /* RoomCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = F075BED41EBB169C00A7B68A /* RoomCollectionViewCell.m */; }; F075BED71EBB169C00A7B68A /* RoomCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F075BED51EBB169C00A7B68A /* RoomCollectionViewCell.xib */; }; F075BEDB1EBB26F100A7B68A /* TableViewCellWithCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = F075BED91EBB26F100A7B68A /* TableViewCellWithCollectionView.m */; }; @@ -492,6 +494,9 @@ F0614A101EDEE65000F5DC9A /* cancel.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cancel.png; sourceTree = ""; }; F0614A111EDEE65000F5DC9A /* cancel@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "cancel@2x.png"; sourceTree = ""; }; F0614A121EDEE65000F5DC9A /* cancel@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "cancel@3x.png"; sourceTree = ""; }; + F06CDD661EF01E3900870B75 /* RoomEmptyBubbleCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RoomEmptyBubbleCell.h; sourceTree = ""; }; + F06CDD671EF01E3900870B75 /* RoomEmptyBubbleCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RoomEmptyBubbleCell.m; sourceTree = ""; }; + F06CDD681EF01E3900870B75 /* RoomEmptyBubbleCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RoomEmptyBubbleCell.xib; sourceTree = ""; }; F075BED31EBB169C00A7B68A /* RoomCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RoomCollectionViewCell.h; sourceTree = ""; }; F075BED41EBB169C00A7B68A /* RoomCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RoomCollectionViewCell.m; sourceTree = ""; }; F075BED51EBB169C00A7B68A /* RoomCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RoomCollectionViewCell.xib; sourceTree = ""; }; @@ -1664,6 +1669,9 @@ isa = PBXGroup; children = ( F083BC751E7009EC00A9B29C /* Encryption */, + F06CDD661EF01E3900870B75 /* RoomEmptyBubbleCell.h */, + F06CDD671EF01E3900870B75 /* RoomEmptyBubbleCell.m */, + F06CDD681EF01E3900870B75 /* RoomEmptyBubbleCell.xib */, F083BCA81E7009EC00A9B29C /* RoomIncomingAttachmentBubbleCell.h */, F083BCA91E7009EC00A9B29C /* RoomIncomingAttachmentBubbleCell.m */, F083BCAA1E7009EC00A9B29C /* RoomIncomingAttachmentBubbleCell.xib */, @@ -2280,6 +2288,7 @@ F083BE571E7009ED00A9B29C /* RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.xib in Resources */, F083BE771E7009ED00A9B29C /* RoomOutgoingTextMsgWithPaginationTitleBubbleCell.xib in Resources */, F083BDA71E7009ED00A9B29C /* notificationsOff.png in Resources */, + F06CDD6A1EF01E3900870B75 /* RoomEmptyBubbleCell.xib in Resources */, F083BDBD1E7009ED00A9B29C /* riot_icon@2x.png in Resources */, F083BDDE1E7009ED00A9B29C /* upload_icon@2x.png in Resources */, F083BE8B1E7009ED00A9B29C /* ExpandedRoomTitleView.xib in Resources */, @@ -2429,6 +2438,7 @@ F083BE741E7009ED00A9B29C /* RoomOutgoingTextMsgWithoutSenderNameBubbleCell.m in Sources */, F083BE1C1E7009ED00A9B29C /* RoomMemberDetailsViewController.m in Sources */, F083BE481E7009ED00A9B29C /* RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, + F06CDD691EF01E3900870B75 /* RoomEmptyBubbleCell.m in Sources */, F083BE041E7009ED00A9B29C /* Tools.m in Sources */, F083BE6A1E7009ED00A9B29C /* RoomOutgoingAttachmentBubbleCell.m in Sources */, F083BDEF1E7009ED00A9B29C /* UINavigationController+Riot.m in Sources */, diff --git a/Riot/Model/Room/RoomBubbleCellData.h b/Riot/Model/Room/RoomBubbleCellData.h index 48dfa5e6b..a59d3103d 100644 --- a/Riot/Model/Room/RoomBubbleCellData.h +++ b/Riot/Model/Room/RoomBubbleCellData.h @@ -38,7 +38,7 @@ @property(nonatomic) NSString *selectedEventId; /** - The index of the most recent component (component with timestamp). NSNotFound by default. + The index of the most recent component (component with a timestamp, and an actual display). NSNotFound by default. */ @property(nonatomic, readonly) NSInteger mostRecentComponentIndex; diff --git a/Riot/Model/Room/RoomBubbleCellData.m b/Riot/Model/Room/RoomBubbleCellData.m index c3ace08a5..36388b619 100644 --- a/Riot/Model/Room/RoomBubbleCellData.m +++ b/Riot/Model/Room/RoomBubbleCellData.m @@ -109,99 +109,92 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; // Refresh the receipt flag during this process _hasReadReceipts = NO; - MXKRoomBubbleComponent *component = [bubbleComponents firstObject]; - NSAttributedString *componentString = component.attributedTextMessage; - -#ifndef DEBUG - // Sanity check: we observed some app crashes due to a nil string in a component. - // According to the implementation this case should not happen because the components are removed as soon as their string is nil. - // We patch here this issue by adding some logs in order to investigate it in the future. - if (!componentString) - { - NSLog(@"[RoomBubbleCellData] WARNING: refreshAttributedTextMessage: unexpected empty component (0/%tu), %@", bubbleComponents.count, component.event.eventId); - componentString = [[NSAttributedString alloc] initWithString:@""]; - } -#endif - NSInteger selectedComponentIndex = self.selectedComponentIndex; NSInteger lastMessageIndex = self.containsLastMessage ? self.mostRecentComponentIndex : NSNotFound; - // Check whether another component than the first one is selected - // Note: When a component is selected, it is highlighted by applying an alpha on other components. - if (selectedComponentIndex != NSNotFound && selectedComponentIndex != 0) + MXKRoomBubbleComponent *component; + NSAttributedString *componentString; + NSUInteger index = 0; + for (; index < bubbleComponents.count; index++) { - // Apply alpha to blur this component - NSMutableAttributedString *customComponentString = [[NSMutableAttributedString alloc] initWithAttributedString:componentString]; - UIColor *color = [componentString attribute:NSForegroundColorAttributeName atIndex:0 effectiveRange:nil]; - color = [color colorWithAlphaComponent:0.2]; - - [customComponentString addAttribute:NSForegroundColorAttributeName value:color range:NSMakeRange(0, customComponentString.length)]; - componentString = customComponentString; - } - - // Check whether the timestamp is displayed for this first component, and check whether a vertical whitespace is required - if ((selectedComponentIndex == 0 || lastMessageIndex == 0) && (self.shouldHideSenderInformation || self.shouldHideSenderName)) - { - currentAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:[RoomBubbleCellData timestampVerticalWhitespace]]; - [currentAttributedTextMsg appendAttributedString:componentString]; - } - else - { - // Init attributed string with the first text component - currentAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:componentString]; - } - - // Vertical whitespace is added in case of read receipts - if ([roomDataSource.room getEventReceipts:component.event.eventId sorted:NO]) - { - _hasReadReceipts = YES; - [currentAttributedTextMsg appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]]; - } - - for (NSUInteger index = 1; index < bubbleComponents.count; index++) - { - [currentAttributedTextMsg appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]]; - component = bubbleComponents[index]; componentString = component.attributedTextMessage; -#ifndef DEBUG - // Sanity check: we observed some app crashes due to a nil string in a component. - // According to the implementation this case should not happen because the components are removed as soon as their string is nil. - // We patch here this issue by adding some logs in order to investigate it in the future. - if (!componentString) + if (componentString) { - NSLog(@"[RoomBubbleCellData] refreshAttributedTextMessage: WARNING: unexpected empty component (%tu/%tu), %@", index, bubbleComponents.count, component.event.eventId); - componentString = [[NSAttributedString alloc] initWithString:@""]; - } -#endif - - // Check whether another component than this one is selected - if (selectedComponentIndex != NSNotFound && selectedComponentIndex != index) - { - // Apply alpha to blur this component - NSMutableAttributedString *customComponentString = [[NSMutableAttributedString alloc] initWithAttributedString:componentString]; - UIColor *color = [componentString attribute:NSForegroundColorAttributeName atIndex:0 effectiveRange:nil]; - color = [color colorWithAlphaComponent:0.2]; + // Check whether another component than this one is selected + // Note: When a component is selected, it is highlighted by applying an alpha on other components. + if (selectedComponentIndex != NSNotFound && selectedComponentIndex != index) + { + // Apply alpha to blur this component + NSMutableAttributedString *customComponentString = [[NSMutableAttributedString alloc] initWithAttributedString:componentString]; + UIColor *color = [componentString attribute:NSForegroundColorAttributeName atIndex:0 effectiveRange:nil]; + color = [color colorWithAlphaComponent:0.2]; + + [customComponentString addAttribute:NSForegroundColorAttributeName value:color range:NSMakeRange(0, customComponentString.length)]; + componentString = customComponentString; + } - [customComponentString addAttribute:NSForegroundColorAttributeName value:color range:NSMakeRange(0, customComponentString.length)]; - componentString = customComponentString; + // Check whether the timestamp is displayed for this component, and check whether a vertical whitespace is required + if ((selectedComponentIndex == index || lastMessageIndex == index) && (self.shouldHideSenderInformation || self.shouldHideSenderName)) + { + currentAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:[RoomBubbleCellData timestampVerticalWhitespace]]; + [currentAttributedTextMsg appendAttributedString:componentString]; + } + else + { + // Init attributed string with the first text component + currentAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:componentString]; + } + + // Vertical whitespace is added in case of read receipts + if ([roomDataSource.room getEventReceipts:component.event.eventId sorted:NO]) + { + _hasReadReceipts = YES; + [currentAttributedTextMsg appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]]; + } + + // The first non empty component has been handled. + break; } + } + + for (index++; index < bubbleComponents.count; index++) + { + component = bubbleComponents[index]; + componentString = component.attributedTextMessage; - // Check whether the timestamp is displayed - if (selectedComponentIndex == index || lastMessageIndex == index) + if (componentString) { - [currentAttributedTextMsg appendAttributedString:[RoomBubbleCellData timestampVerticalWhitespace]]; - } - - // Append attributed text - [currentAttributedTextMsg appendAttributedString:componentString]; - - // Add vertical whitespace in case of read receipts - if ([roomDataSource.room getEventReceipts:component.event.eventId sorted:NO]) - { - _hasReadReceipts = YES; - [currentAttributedTextMsg appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]]; + [currentAttributedTextMsg appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]]; + + // Check whether another component than this one is selected + if (selectedComponentIndex != NSNotFound && selectedComponentIndex != index) + { + // Apply alpha to blur this component + NSMutableAttributedString *customComponentString = [[NSMutableAttributedString alloc] initWithAttributedString:componentString]; + UIColor *color = [componentString attribute:NSForegroundColorAttributeName atIndex:0 effectiveRange:nil]; + color = [color colorWithAlphaComponent:0.2]; + + [customComponentString addAttribute:NSForegroundColorAttributeName value:color range:NSMakeRange(0, customComponentString.length)]; + componentString = customComponentString; + } + + // Check whether the timestamp is displayed + if (selectedComponentIndex == index || lastMessageIndex == index) + { + [currentAttributedTextMsg appendAttributedString:[RoomBubbleCellData timestampVerticalWhitespace]]; + } + + // Append attributed text + [currentAttributedTextMsg appendAttributedString:componentString]; + + // Add vertical whitespace in case of read receipts + if ([roomDataSource.room getEventReceipts:component.event.eventId sorted:NO]) + { + _hasReadReceipts = YES; + [currentAttributedTextMsg appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]]; + } } } @@ -221,22 +214,32 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; if (bubbleComponents.count) { // Set position of the first component - MXKRoomBubbleComponent *component = [bubbleComponents firstObject]; - CGFloat positionY = (self.attachment == nil || self.attachment.type == MXKAttachmentTypeFile) ? MXKROOMBUBBLECELLDATA_TEXTVIEW_DEFAULT_VERTICAL_INSET : 0; - component.position = CGPointMake(0, positionY); - - _hasReadReceipts = ([roomDataSource.room getEventReceipts:component.event.eventId sorted:NO] != nil); + MXKRoomBubbleComponent *component; + NSUInteger index = 0; + for (; index < bubbleComponents.count; index++) + { + // Compute the vertical position for next component + component = [bubbleComponents objectAtIndex:index]; + + component.position = CGPointMake(0, positionY); + + if (component.attributedTextMessage) + { + _hasReadReceipts = ([roomDataSource.room getEventReceipts:component.event.eventId sorted:NO] != nil); + break; + } + } // Check whether the position of other components need to be refreshed - if (!self.attachment && bubbleComponents.count > 1) + if (!self.attachment && index < bubbleComponents.count) { NSMutableAttributedString *attributedString; NSInteger selectedComponentIndex = self.selectedComponentIndex; NSInteger lastMessageIndex = self.containsLastMessage ? self.mostRecentComponentIndex : NSNotFound; // Check whether the timestamp is displayed for this first component, and check whether a vertical whitespace is required - if ((selectedComponentIndex == 0 || lastMessageIndex == 0) && (self.shouldHideSenderInformation || self.shouldHideSenderName)) + if ((selectedComponentIndex == index || lastMessageIndex == index) && (self.shouldHideSenderInformation || self.shouldHideSenderName)) { attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:[RoomBubbleCellData timestampVerticalWhitespace]]; [attributedString appendAttributedString:component.attributedTextMessage]; @@ -255,44 +258,51 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; [attributedString appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]]; - for (NSUInteger index = 1; index < bubbleComponents.count; index++) + for (index++; index < bubbleComponents.count; index++) { // Compute the vertical position for next component component = [bubbleComponents objectAtIndex:index]; - // Prepare its attributed string by considering potential vertical margin required to display timestamp. - NSAttributedString *componentString; - if (selectedComponentIndex == index || lastMessageIndex == index) + if (component.attributedTextMessage) { - NSMutableAttributedString *componentAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:[RoomBubbleCellData timestampVerticalWhitespace]]; - [componentAttributedString appendAttributedString:component.attributedTextMessage]; + // Prepare its attributed string by considering potential vertical margin required to display timestamp. + NSAttributedString *componentString; + if (selectedComponentIndex == index || lastMessageIndex == index) + { + NSMutableAttributedString *componentAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:[RoomBubbleCellData timestampVerticalWhitespace]]; + [componentAttributedString appendAttributedString:component.attributedTextMessage]; + + componentString = componentAttributedString; + } + else + { + componentString = component.attributedTextMessage; + } - componentString = componentAttributedString; + // Append this attributed string. + [attributedString appendAttributedString:componentString]; + + // Compute the height of the resulting string. + CGFloat cumulatedHeight = [self rawTextHeight:attributedString]; + + // Deduce the position of the beginning of this component. + positionY = MXKROOMBUBBLECELLDATA_TEXTVIEW_DEFAULT_VERTICAL_INSET + (cumulatedHeight - [self rawTextHeight:componentString]); + + component.position = CGPointMake(0, positionY); + + // Add vertical whitespace in case of read receipts. + if ([roomDataSource.room getEventReceipts:component.event.eventId sorted:NO]) + { + _hasReadReceipts = YES; + [attributedString appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]]; + } + + [attributedString appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]]; } else { - componentString = component.attributedTextMessage; + component.position = CGPointMake(0, positionY); } - - // Append this attributed string. - [attributedString appendAttributedString:componentString]; - - // Compute the height of the resulting string. - CGFloat cumulatedHeight = [self rawTextHeight:attributedString]; - - // Deduce the position of the beginning of this component. - CGFloat positionY = MXKROOMBUBBLECELLDATA_TEXTVIEW_DEFAULT_VERTICAL_INSET + (cumulatedHeight - [self rawTextHeight:componentString]); - - component.position = CGPointMake(0, positionY); - - // Add vertical whitespace in case of read receipts. - if ([roomDataSource.room getEventReceipts:component.event.eventId sorted:NO]) - { - _hasReadReceipts = YES; - [attributedString appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]]; - } - - [attributedString appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]]; } } } @@ -348,7 +358,7 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; while (index--) { MXKRoomBubbleComponent *component = components[index]; - if (component.date) + if (component.attributedTextMessage && component.date) { mostRecentComponentIndex = index; break; diff --git a/Riot/Model/Room/RoomDataSource.m b/Riot/Model/Room/RoomDataSource.m index 3f36957f2..4a1a71d77 100644 --- a/Riot/Model/Room/RoomDataSource.m +++ b/Riot/Model/Room/RoomDataSource.m @@ -41,14 +41,15 @@ self.eventFormatter.treatMatrixUserIdAsLink = YES; self.eventFormatter.treatMatrixRoomIdAsLink = YES; self.eventFormatter.treatMatrixRoomAliasAsLink = YES; + + // Apply the event types filter to display only the wanted event types. + self.eventFormatter.eventTypesFilterForMessages = [MXKAppSettings standardAppSettings].eventsFilterForMessages; // Handle timestamp and read receips display at Vector app level (see [tableView: cellForRowAtIndexPath:]) self.useCustomDateTimeLabel = YES; self.useCustomReceipts = YES; self.useCustomUnsentButton = YES; - // TODO custom here self.eventsFilterForMessages according to Vector requirements - // Set bubble pagination self.bubblesPagination = MXKRoomDataSourceBubblesPaginationPerDay; @@ -72,7 +73,8 @@ for (NSString* eventId in readEventIds) { RoomBubbleCellData *cellData = [self cellDataOfEventWithEventId:eventId]; - cellData.hasReadReceipts = YES; + // Ignore the read receipts on the events without an actual display. + cellData.hasReadReceipts = !cellData.hasNoDisplay; } [super didReceiveReceiptEvent:receiptEvent roomState:roomState]; @@ -95,9 +97,17 @@ cellData.containsLastMessage = NO; } - // The cell containing the last message is the last one - RoomBubbleCellData *cellData = bubbles.lastObject; - cellData.containsLastMessage = YES; + // The cell containing the last message is the last one with an actual display. + NSInteger index = bubbles.count; + while (index--) + { + RoomBubbleCellData *cellData = bubbles[index]; + if (cellData.attributedTextMessage) + { + cellData.containsLastMessage = YES; + break; + } + } } } @@ -131,6 +141,7 @@ } // Handle read receipts and read marker display. + // Ignore the read receipts on the bubble without actual display. if ((self.showBubbleReceipts && cellData.hasReadReceipts) || self.showReadMarker) { // Read receipts container are inserted here on the right side into the overlay container. @@ -148,82 +159,86 @@ if (component.event.sentState != MXEventSentStateFailed) { - // Get the events receipts by ignoring the current user receipt. - NSArray* receipts = [self.room getEventReceipts:component.event.eventId sorted:YES]; - NSMutableArray *roomMembers; - NSMutableArray *placeholders; - - // Check whether some receipts are found - if (receipts.count) + // Handle read receipts (if any) + if (self.showBubbleReceipts && cellData.hasReadReceipts) { - // Retrieve the corresponding room members - roomMembers = [[NSMutableArray alloc] initWithCapacity:receipts.count]; - placeholders = [[NSMutableArray alloc] initWithCapacity:receipts.count]; + // Get the events receipts by ignoring the current user receipt. + NSArray* receipts = [self.room getEventReceipts:component.event.eventId sorted:YES]; + NSMutableArray *roomMembers; + NSMutableArray *placeholders; - for (MXReceiptData* data in receipts) + // Check whether some receipts are found + if (receipts.count) { - MXRoomMember * roomMember = [self.room.state memberWithUserId:data.userId]; - if (roomMember) + // Retrieve the corresponding room members + roomMembers = [[NSMutableArray alloc] initWithCapacity:receipts.count]; + placeholders = [[NSMutableArray alloc] initWithCapacity:receipts.count]; + + for (MXReceiptData* data in receipts) { - [roomMembers addObject:roomMember]; - [placeholders addObject:[AvatarGenerator generateAvatarForMatrixItem:roomMember.userId withDisplayName:roomMember.displayname]]; + MXRoomMember * roomMember = [self.room.state memberWithUserId:data.userId]; + if (roomMember) + { + [roomMembers addObject:roomMember]; + [placeholders addObject:[AvatarGenerator generateAvatarForMatrixItem:roomMember.userId withDisplayName:roomMember.displayname]]; + } } } - } - - // Check whether some receipts are found - if (roomMembers.count) - { - // Define the read receipts container, positioned on the right border of the bubble cell (Note the right margin 6 pts). - MXKReceiptSendersContainer* avatarsContainer = [[MXKReceiptSendersContainer alloc] initWithFrame:CGRectMake(bubbleCell.frame.size.width - 156, bottomPositionY - 13, 150, 12) andRestClient:self.mxSession.matrixRestClient]; - // Custom avatar display - avatarsContainer.maxDisplayedAvatars = 5; - avatarsContainer.avatarMargin = 6; - - // Set the container tag to be able to retrieve read receipts container from component index (see component selection in MXKRoomBubbleTableViewCell (Vector) category). - avatarsContainer.tag = index; - - [avatarsContainer refreshReceiptSenders:roomMembers withPlaceHolders:placeholders andAlignment:ReadReceiptAlignmentRight]; - - avatarsContainer.translatesAutoresizingMaskIntoConstraints = NO; - avatarsContainer.accessibilityIdentifier = @"readReceiptsContainer"; - [bubbleCell.bubbleOverlayContainer addSubview:avatarsContainer]; - - // Force receipts container size - NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1.0 - constant:150]; - NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1.0 - constant:12]; - - // Force receipts container position - NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer - attribute:NSLayoutAttributeTrailing - relatedBy:NSLayoutRelationEqual - toItem:bubbleCell.bubbleOverlayContainer - attribute:NSLayoutAttributeTrailing - multiplier:1.0 - constant:-6]; - NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:bubbleCell.bubbleOverlayContainer - attribute:NSLayoutAttributeTop - multiplier:1.0 - constant:bottomPositionY - 13]; - - // Available on iOS 8 and later - [NSLayoutConstraint activateConstraints:@[widthConstraint, heightConstraint, topConstraint, trailingConstraint]]; + // Check whether some receipts are found + if (roomMembers.count) + { + // Define the read receipts container, positioned on the right border of the bubble cell (Note the right margin 6 pts). + MXKReceiptSendersContainer* avatarsContainer = [[MXKReceiptSendersContainer alloc] initWithFrame:CGRectMake(bubbleCell.frame.size.width - 156, bottomPositionY - 13, 150, 12) andRestClient:self.mxSession.matrixRestClient]; + + // Custom avatar display + avatarsContainer.maxDisplayedAvatars = 5; + avatarsContainer.avatarMargin = 6; + + // Set the container tag to be able to retrieve read receipts container from component index (see component selection in MXKRoomBubbleTableViewCell (Vector) category). + avatarsContainer.tag = index; + + [avatarsContainer refreshReceiptSenders:roomMembers withPlaceHolders:placeholders andAlignment:ReadReceiptAlignmentRight]; + + avatarsContainer.translatesAutoresizingMaskIntoConstraints = NO; + avatarsContainer.accessibilityIdentifier = @"readReceiptsContainer"; + [bubbleCell.bubbleOverlayContainer addSubview:avatarsContainer]; + + // Force receipts container size + NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:150]; + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:12]; + + // Force receipts container position + NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer + attribute:NSLayoutAttributeTrailing + relatedBy:NSLayoutRelationEqual + toItem:bubbleCell.bubbleOverlayContainer + attribute:NSLayoutAttributeTrailing + multiplier:1.0 + constant:-6]; + NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:bubbleCell.bubbleOverlayContainer + attribute:NSLayoutAttributeTop + multiplier:1.0 + constant:bottomPositionY - 13]; + + // Available on iOS 8 and later + [NSLayoutConstraint activateConstraints:@[widthConstraint, heightConstraint, topConstraint, trailingConstraint]]; + } } // Check whether the read marker must be displayed here. diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 46ba847bc..28a1b3ddb 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -45,6 +45,8 @@ #import "UsersDevicesViewController.h" +#import "RoomEmptyBubbleCell.h" + #import "RoomIncomingTextMsgBubbleCell.h" #import "RoomIncomingTextMsgWithoutSenderInfoBubbleCell.h" #import "RoomIncomingTextMsgWithPaginationTitleBubbleCell.h" @@ -269,6 +271,8 @@ [self.bubblesTableView registerClass:RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.defaultReuseIdentifier]; [self.bubblesTableView registerClass:RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:RoomEmptyBubbleCell.class forCellReuseIdentifier:RoomEmptyBubbleCell.defaultReuseIdentifier]; + // Prepare jump to last unread banner self.jumpToLastUnreadLabel.attributedText = [[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"room_jump_to_first_unread", @"Vector", nil) attributes:@{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle), NSUnderlineColorAttributeName: kRiotTextColorBlack, NSForegroundColorAttributeName: kRiotTextColorBlack}]; @@ -1504,8 +1508,12 @@ { id bubbleData = (id)cellData; - // Select the suitable table view cell class - if (bubbleData.isIncoming) + // Select the suitable table view cell class, by considering first the empty bubble cell. + if (!bubbleData.attributedTextMessage) + { + cellViewClass = RoomEmptyBubbleCell.class; + } + else if (bubbleData.isIncoming) { if (bubbleData.isAttachmentWithThumbnail) { @@ -2512,7 +2520,8 @@ // Move the read marker to the current read receipt position. [self.roomDataSource.room forgetReadMarker]; - [self refreshJumpToLastUnreadBannerDisplay]; + // Hide the banner + self.jumpToLastUnreadBannerContainer.hidden = YES; } } diff --git a/Riot/Views/RoomBubbleList/RoomEmptyBubbleCell.h b/Riot/Views/RoomBubbleList/RoomEmptyBubbleCell.h new file mode 100644 index 000000000..292b5fec5 --- /dev/null +++ b/Riot/Views/RoomBubbleList/RoomEmptyBubbleCell.h @@ -0,0 +1,24 @@ +/* + Copyright 2017 Vector Creations Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +/** + `RoomEmptyBubbleCell` displays empty bubbles. + */ +@interface RoomEmptyBubbleCell : MXKRoomEmptyBubbleTableViewCell + +@end diff --git a/Riot/Views/RoomBubbleList/RoomEmptyBubbleCell.m b/Riot/Views/RoomBubbleList/RoomEmptyBubbleCell.m new file mode 100644 index 000000000..fdb0e0904 --- /dev/null +++ b/Riot/Views/RoomBubbleList/RoomEmptyBubbleCell.m @@ -0,0 +1,21 @@ +/* + Copyright 2017 Vector Creations Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "RoomEmptyBubbleCell.h" + +@implementation RoomEmptyBubbleCell + +@end diff --git a/Riot/Views/RoomBubbleList/RoomEmptyBubbleCell.xib b/Riot/Views/RoomBubbleList/RoomEmptyBubbleCell.xib new file mode 100644 index 000000000..1b05a7e0b --- /dev/null +++ b/Riot/Views/RoomBubbleList/RoomEmptyBubbleCell.xib @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +