diff --git a/Riot/Modules/Room/MXKRoomViewController.m b/Riot/Modules/Room/MXKRoomViewController.m index 8d0d2808a..90e1d0233 100644 --- a/Riot/Modules/Room/MXKRoomViewController.m +++ b/Riot/Modules/Room/MXKRoomViewController.m @@ -2489,24 +2489,9 @@ if (acknowledge && self.isEventsAcknowledgementEnabled) { // Indicate to the homeserver that the user has read this event. - - // Check whether the read marker must be updated. - BOOL updateReadMarker = _updateRoomReadMarker; - if (updateReadMarker && roomDataSource.room.accountData.readMarkerEventId) - { - MXEvent *currentReadMarkerEvent = [roomDataSource.mxSession.store eventWithEventId:roomDataSource.room.accountData.readMarkerEventId inRoom:roomDataSource.roomId]; - if (!currentReadMarkerEvent) - { - currentReadMarkerEvent = [roomDataSource eventWithEventId:roomDataSource.room.accountData.readMarkerEventId]; - } - - // Update the read marker only if the current event is available, and the new event is posterior to it. - updateReadMarker = (currentReadMarkerEvent && (currentReadMarkerEvent.originServerTs <= component.event.originServerTs)); - } - if (self.navigationController.viewControllers.lastObject == self) { - [roomDataSource.room acknowledgeEvent:component.event andUpdateReadMarker:updateReadMarker]; + [roomDataSource.room acknowledgeEvent:component.event andUpdateReadMarker:NO]; } } break; @@ -2515,6 +2500,139 @@ } } } + + [self updateReadMarkerEventIdAtTableBottom]; +} + +- (void)updateReadMarkerEventIdAtTableBottom +{ + if (!_updateRoomReadMarker) + { + return; + } + + // Do not update events if the controller is used as context menu preview. + if (self.isContextPreview) + { + return; + } + + // Update the identifier of the event displayed at the bottom of the table, except if a rotation or other size transition is in progress. + if (isSizeTransitionInProgress || self.isBubbleTableViewDisplayInTransition) + { + return; + } + + // Compute the content offset corresponding to the line displayed at the table bottom (just above the toolbar). + CGFloat contentBottomOffsetY = _bubblesTableView.contentOffset.y + (_bubblesTableView.frame.size.height - _bubblesTableView.adjustedContentInset.bottom); + if (contentBottomOffsetY > _bubblesTableView.contentSize.height) + { + contentBottomOffsetY = _bubblesTableView.contentSize.height; + } + // Be a bit less retrictive, consider visible an event at the bottom even if is partially hidden. + contentBottomOffsetY += 8; + + // Reset the current event id + currentEventIdAtTableBottom = nil; + + // Consider the visible cells (starting by those displayed at the bottom) + NSArray *visibleCells = [_bubblesTableView visibleCells]; + NSInteger index = visibleCells.count; + UITableViewCell *cell; + while (index--) + { + cell = visibleCells[index]; + + // Check whether the cell is actually visible + if (cell && (cell.frame.origin.y < contentBottomOffsetY)) + { + if (![cell isKindOfClass:MXKTableViewCell.class]) + { + continue; + } + + MXKCellData *cellData = ((MXKTableViewCell *)cell).mxkCellData; + + // Only 'MXKRoomBubbleCellData' is supported here for the moment. + if (![cellData isKindOfClass:MXKRoomBubbleCellData.class]) + { + continue; + } + + MXKRoomBubbleCellData *bubbleData = (MXKRoomBubbleCellData*)cellData; + + // Prevent to place the read marker on a collapsed cell + if (bubbleData.collapsed) + { + continue; + } + + // Check which bubble component is displayed at the bottom. + // For that update each component position. + [bubbleData prepareBubbleComponentsPosition]; + + NSArray *bubbleComponents = bubbleData.bubbleComponents; + NSInteger componentIndex = bubbleComponents.count; + + CGFloat bottomPositionY = cell.frame.size.height; + + MXKRoomBubbleComponent *component; + + while (componentIndex --) + { + component = bubbleComponents[componentIndex]; + if (![cell isKindOfClass:MXKRoomBubbleTableViewCell.class]) + { + continue; + } + + // Prevent the readmarker to be placed on a relatesTo or a redaction event + if (component.event.relatesTo || component.event.redacts) + { + continue; + } + + MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell *)cell; + + // Check whether the bottom part of the component is visible. + CGFloat pos = cell.frame.origin.y + bottomPositionY; + if (pos <= contentBottomOffsetY) + { + // We found the component + currentEventIdAtTableBottom = component.event.eventId; + break; + } + + // Prepare the bottom position for the next component + bottomPositionY = roomBubbleTableViewCell.msgTextViewTopConstraint.constant + component.position.y; + } + + if (currentEventIdAtTableBottom) + { + // Check whether the read marker must be updated. + BOOL updateReadMarker = YES; + if (roomDataSource.room.accountData.readMarkerEventId) + { + MXEvent *currentReadMarkerEvent = [roomDataSource.mxSession.store eventWithEventId:roomDataSource.room.accountData.readMarkerEventId inRoom:roomDataSource.roomId]; + if (!currentReadMarkerEvent) + { + currentReadMarkerEvent = [roomDataSource eventWithEventId:roomDataSource.room.accountData.readMarkerEventId]; + } + + // Update the read marker only if the current event is available, and the new event is posterior to it. + updateReadMarker = currentReadMarkerEvent && (currentReadMarkerEvent.eventId != component.event.eventId) && (currentReadMarkerEvent.originServerTs <= component.event.originServerTs); + } + + if (updateReadMarker && self.navigationController.viewControllers.lastObject == self) + { + [roomDataSource.room moveReadMarkerToEventId:component.event.eventId]; + } + + break; + } + // else we consider the previous cell. + } + } } #pragma mark - MXKDataSourceDelegate diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 3819a7a49..65303d7af 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -6612,8 +6612,12 @@ static CGSize kThreadListBarButtonItemImageSize; // Check whether the read marker exists and has not been rendered yet. if (self.roomDataSource.isLive && !self.roomDataSource.isPeeking && self.roomDataSource.showReadMarker && self.roomDataSource.room.accountData.readMarkerEventId) { - UITableViewCell *cell = [self.bubblesTableView visibleCells].firstObject; - if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class] && ![cell isKindOfClass:MXKRoomEmptyBubbleTableViewCell.class]) + NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { + return [evaluatedObject isKindOfClass:MXKRoomBubbleTableViewCell.class]; + }]; + NSArray *visibleCells = [[self.bubblesTableView visibleCells] filteredArrayUsingPredicate:predicate]; + UITableViewCell *cell = visibleCells.firstObject; + if (cell) { MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell*)cell; // Check whether the read marker is inside the first displayed cell. @@ -6654,6 +6658,39 @@ static CGSize kThreadListBarButtonItemImageSize; // Move the read marker to the current read receipt position by default. [self.roomDataSource.room forgetReadMarker]; } + // Force the update of the read marker if we try to display it (in some cases, we are not able to setup the readMarkerView which prevent us to update the read marker) + else if (!self.roomDataSource.isLive && !self.roomDataSource.isPeeking && self.roomDataSource.showReadMarker && self.roomDataSource.room.accountData.readMarkerEventId && !self.updateRoomReadMarker) + { + // Check if the readmarker's event is in a visible cell + MXEvent *currentReadMarkerEvent = [self.roomDataSource.mxSession.store eventWithEventId:self.roomDataSource.room.accountData.readMarkerEventId inRoom:self.roomDataSource.roomId]; + if (currentReadMarkerEvent) + { + NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { + return [evaluatedObject isKindOfClass:MXKRoomBubbleTableViewCell.class]; + }]; + NSArray *visibleCells = [[self.bubblesTableView visibleCells] filteredArrayUsingPredicate:predicate]; + UITableViewCell *firstCell = visibleCells.firstObject; + UITableViewCell *lastCell = visibleCells.lastObject; + if (firstCell && lastCell) + { + // Get the first displayed event + MXKRoomBubbleTableViewCell *firstRoomBubbleTableViewCell = (MXKRoomBubbleTableViewCell*)firstCell; + MXKRoomBubbleComponent *component = firstRoomBubbleTableViewCell.bubbleData.bubbleComponents.firstObject; + MXEvent *firstDisplayedEvent = component.event; + + // Get the last displayed event + MXKRoomBubbleTableViewCell *lastRoomBubbleTableViewCell = (MXKRoomBubbleTableViewCell*)lastCell; + MXKRoomBubbleComponent *lastComponent = lastRoomBubbleTableViewCell.bubbleData.bubbleComponents.lastObject; + MXEvent *lastDisplayedEvent = lastComponent.event; + + // If the currentReadMarkerEvent is between the first and the last displayed event, force the update of the room read marker to prevent it from being blocked + if (currentReadMarkerEvent.originServerTs > firstDisplayedEvent.originServerTs && currentReadMarkerEvent.originServerTs < lastDisplayedEvent.originServerTs) + { + self.updateRoomReadMarker = YES; + } + } + } + } } } diff --git a/changelog.d/7420.bugfix b/changelog.d/7420.bugfix new file mode 100644 index 000000000..5238e7dc9 --- /dev/null +++ b/changelog.d/7420.bugfix @@ -0,0 +1 @@ +Update the read marker position even if it is not displayed