element-ios/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataWithAppendingMode.m
2022-01-04 17:33:32 +02:00

356 lines
13 KiB
Objective-C

/*
Copyright 2015 OpenMarket 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 "MXKRoomBubbleCellDataWithAppendingMode.h"
static NSAttributedString *messageSeparator = nil;
@implementation MXKRoomBubbleCellDataWithAppendingMode
#pragma mark - MXKRoomBubbleCellDataStoring
- (instancetype)initWithEvent:(MXEvent *)event andRoomState:(MXRoomState *)roomState andRoomDataSource:(MXKRoomDataSource *)roomDataSource2
{
self = [super initWithEvent:event andRoomState:roomState andRoomDataSource:roomDataSource2];
if (self)
{
// Set default settings
self.maxComponentCount = 10;
}
return self;
}
- (BOOL)addEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState
{
// We group together text messages from the same user (attachments are not merged).
if ([event.sender isEqualToString:self.senderId] && (self.attachment == nil) && (self.bubbleComponents.count < self.maxComponentCount))
{
// Attachments (image, video, sticker ...) cannot be added here
if ([roomDataSource.eventFormatter isSupportedAttachment:event])
{
return NO;
}
// Check sender information
NSString *eventSenderName = [roomDataSource.eventFormatter senderDisplayNameForEvent:event withRoomState:roomState];
NSString *eventSenderAvatar = [roomDataSource.eventFormatter senderAvatarUrlForEvent:event withRoomState:roomState];
if ((self.senderDisplayName || eventSenderName) &&
([self.senderDisplayName isEqualToString:eventSenderName] == NO))
{
return NO;
}
if ((self.senderAvatarUrl || eventSenderAvatar) &&
([self.senderAvatarUrl isEqualToString:eventSenderAvatar] == NO))
{
return NO;
}
// Take into account here the rendered bubbles pagination
if (roomDataSource.bubblesPagination == MXKRoomDataSourceBubblesPaginationPerDay)
{
// Event must be sent the same day than the existing bubble.
NSString *bubbleDateString = [roomDataSource.eventFormatter dateStringFromDate:self.date withTime:NO];
NSString *eventDateString = [roomDataSource.eventFormatter dateStringFromEvent:event withTime:NO];
if (bubbleDateString && eventDateString && ![bubbleDateString isEqualToString:eventDateString])
{
return NO;
}
}
// Create new message component
MXKRoomBubbleComponent *addedComponent = [[MXKRoomBubbleComponent alloc] initWithEvent:event roomState:roomState eventFormatter:roomDataSource.eventFormatter session:self.mxSession];
if (addedComponent)
{
[self addComponent:addedComponent];
}
// else the event is ignored, we consider it as handled
return YES;
}
return NO;
}
- (BOOL)mergeWithBubbleCellData:(id<MXKRoomBubbleCellDataStoring>)bubbleCellData
{
if ([self hasSameSenderAsBubbleCellData:bubbleCellData])
{
MXKRoomBubbleCellData *cellData = (MXKRoomBubbleCellData*)bubbleCellData;
// Only text messages are merged (Attachments are not merged).
if ((self.attachment == nil) && (cellData.attachment == nil))
{
// Take into account here the rendered bubbles pagination
if (roomDataSource.bubblesPagination == MXKRoomDataSourceBubblesPaginationPerDay)
{
// bubble components must be sent the same day than self.
NSString *selfDateString = [roomDataSource.eventFormatter dateStringFromDate:self.date withTime:NO];
NSString *bubbleDateString = [roomDataSource.eventFormatter dateStringFromDate:bubbleCellData.date withTime:NO];
if (![bubbleDateString isEqualToString:selfDateString])
{
return NO;
}
}
// Add all components of the provided message
for (MXKRoomBubbleComponent* component in cellData.bubbleComponents)
{
[self addComponent:component];
}
return YES;
}
}
return NO;
}
- (NSAttributedString*)attributedTextMessageWithHighlightedEvent:(NSString*)eventId tintColor:(UIColor*)tintColor
{
// Create attributed string
NSMutableAttributedString *customAttributedTextMsg;
NSAttributedString *componentString;
@synchronized(bubbleComponents)
{
for (MXKRoomBubbleComponent* component in bubbleComponents)
{
componentString = component.attributedTextMessage;
if (componentString)
{
if ([component.event.eventId isEqualToString:eventId])
{
NSMutableAttributedString *customComponentString = [[NSMutableAttributedString alloc] initWithAttributedString:componentString];
UIColor *color = tintColor ? tintColor : [UIColor lightGrayColor];
[customComponentString addAttribute:NSBackgroundColorAttributeName value:color range:NSMakeRange(0, customComponentString.length)];
componentString = customComponentString;
}
if (!customAttributedTextMsg)
{
customAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:componentString];
}
else
{
// Append attributed text
[customAttributedTextMsg appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]];
[customAttributedTextMsg appendAttributedString:componentString];
}
}
}
}
return customAttributedTextMsg;
}
#pragma mark -
- (void)prepareBubbleComponentsPosition
{
// Set position of the first component
[super prepareBubbleComponentsPosition];
@synchronized(bubbleComponents)
{
// Check whether the position of other components need to be refreshed
if (!self.attachment && shouldUpdateComponentsPosition && bubbleComponents.count > 1)
{
// Init attributed string with the first text component not nil.
MXKRoomBubbleComponent *component = bubbleComponents.firstObject;
CGFloat positionY = component.position.y;
NSMutableAttributedString *attributedString;
NSUInteger index = 0;
for (; index < bubbleComponents.count; index++)
{
component = [bubbleComponents objectAtIndex:index];
component.position = CGPointMake(0, positionY);
if (component.attributedTextMessage)
{
attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:component.attributedTextMessage];
[attributedString appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]];
break;
}
}
for (index++; index < bubbleComponents.count; index++)
{
// Append the next text component
component = [bubbleComponents objectAtIndex:index];
if (component.attributedTextMessage)
{
[attributedString appendAttributedString:component.attributedTextMessage];
// 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:component.attributedTextMessage]);
component.position = CGPointMake(0, positionY);
[attributedString appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]];
}
else
{
// Apply the current vertical position on this empty component.
component.position = CGPointMake(0, positionY);
}
}
}
}
shouldUpdateComponentsPosition = NO;
}
#pragma mark -
- (NSString*)textMessage
{
NSString *rawText = nil;
if (self.attributedTextMessage)
{
// Append all components text message
NSMutableString *currentTextMsg;
@synchronized(bubbleComponents)
{
for (MXKRoomBubbleComponent* component in bubbleComponents)
{
if (component.textMessage == nil)
{
continue;
}
if (!currentTextMsg)
{
currentTextMsg = [NSMutableString stringWithString:component.textMessage];
}
else
{
// Append text message
[currentTextMsg appendString:@"\n"];
[currentTextMsg appendString:component.textMessage];
}
}
}
rawText = currentTextMsg;
}
return rawText;
}
- (void)setAttributedTextMessage:(NSAttributedString *)inAttributedTextMessage
{
super.attributedTextMessage = inAttributedTextMessage;
// Position of each components should be computed again
shouldUpdateComponentsPosition = YES;
}
- (NSAttributedString*)attributedTextMessage
{
@synchronized(bubbleComponents)
{
if (self.hasAttributedTextMessage && !attributedTextMessage.length)
{
// Create attributed string
NSMutableAttributedString *currentAttributedTextMsg;
for (MXKRoomBubbleComponent* component in bubbleComponents)
{
if (component.attributedTextMessage)
{
if (!currentAttributedTextMsg)
{
currentAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:component.attributedTextMessage];
}
else
{
// Append attributed text
[currentAttributedTextMsg appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]];
[currentAttributedTextMsg appendAttributedString:component.attributedTextMessage];
}
}
}
self.attributedTextMessage = currentAttributedTextMsg;
}
}
return attributedTextMessage;
}
- (void)setMaxTextViewWidth:(CGFloat)inMaxTextViewWidth
{
CGFloat previousMaxWidth = self.maxTextViewWidth;
[super setMaxTextViewWidth:inMaxTextViewWidth];
// Check change
if (previousMaxWidth != self.maxTextViewWidth)
{
// Position of each components should be computed again
shouldUpdateComponentsPosition = YES;
}
}
#pragma mark -
+ (NSAttributedString *)messageSeparator
{
@synchronized(self)
{
if(messageSeparator == nil)
{
messageSeparator = [[NSAttributedString alloc] initWithString:@"\n\n" attributes:@{NSForegroundColorAttributeName : [UIColor blackColor],
NSFontAttributeName: [UIFont systemFontOfSize:4]}];
}
}
return messageSeparator;
}
#pragma mark - Privates
- (void)addComponent:(MXKRoomBubbleComponent*)addedComponent
{
@synchronized(bubbleComponents)
{
// Check date of existing components to insert this new one
NSUInteger index = bubbleComponents.count;
// Component without date is added at the end by default
if (addedComponent.date)
{
while (index)
{
MXKRoomBubbleComponent *msgComponent = [bubbleComponents objectAtIndex:(--index)];
if (msgComponent.date && [msgComponent.date compare:addedComponent.date] != NSOrderedDescending)
{
// New component will be inserted here
index ++;
break;
}
}
}
// Insert new component
[bubbleComponents insertObject:addedComponent atIndex:index];
// Indicate that the data's text message layout should be recomputed.
[self invalidateTextLayout];
}
}
@end