element-ios/Vector/Model/Room/RoomBubbleCellData.m

360 lines
15 KiB
Mathematica
Raw Normal View History

/*
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 "RoomBubbleCellData.h"
#import "EventFormatter.h"
#import "AvatarGenerator.h"
static NSAttributedString *timestampVerticalWhitespace = nil;
static NSAttributedString *readReceiptVerticalWhitespace = nil;
@implementation RoomBubbleCellData
#pragma mark - Override MXKRoomBubbleCellData
- (instancetype)initWithEvent:(MXEvent *)event andRoomState:(MXRoomState *)roomState andRoomDataSource:(MXKRoomDataSource *)roomDataSource2
{
self = [super initWithEvent:event andRoomState:roomState andRoomDataSource:roomDataSource2];
if (self)
{
// Use the vector style placeholder
self.senderAvatarPlaceholder = [AvatarGenerator generateRoomMemberAvatar:self.senderId displayName:self.senderDisplayName];
// Increase maximum number of components
self.maxComponentCount = 20;
// Check whether some read receipts are linked to this event
_hasReadReceipts = NO;
if ([roomDataSource.room getEventReceipts:event.eventId sorted:NO])
{
_hasReadReceipts = YES;
// Update attributed string by inserting vertical whitespace at the end to display read receipts
NSMutableAttributedString *updatedAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:attributedTextMessage];
[updatedAttributedTextMsg appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]];
// Update the current text message by reseting content size
self.attributedTextMessage = updatedAttributedTextMsg;
}
}
return self;
}
- (void)prepareBubbleComponentsPosition
{
if (shouldUpdateComponentsPosition)
{
// Refresh the receipt flag during this process.
_hasReadReceipts = NO;
@synchronized(bubbleComponents)
{
// Check whether there is at least one component.
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);
// Check whether the position of other components need to be refreshed
if (!self.attachment && bubbleComponents.count > 1)
{
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))
{
attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:[RoomBubbleCellData timestampVerticalWhitespace]];
[attributedString appendAttributedString:component.attributedTextMessage];
}
else
{
// Init attributed string with the first text component
attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:component.attributedTextMessage];
}
// Vertical whitespace is added in case of read receipts
if (_hasReadReceipts)
{
[attributedString appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]];
}
[attributedString appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]];
for (NSUInteger index = 1; 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)
{
NSMutableAttributedString *componentAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:[RoomBubbleCellData timestampVerticalWhitespace]];
[componentAttributedString appendAttributedString:component.attributedTextMessage];
componentString = componentAttributedString;
}
else
{
componentString = component.attributedTextMessage;
}
// 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]];
}
}
}
}
shouldUpdateComponentsPosition = NO;
}
}
- (NSAttributedString*)attributedTextMessage
{
// Note: When a component is selected, it is highlighted by applying an alpha on other components.
@synchronized(bubbleComponents)
{
if (!attributedTextMessage.length && bubbleComponents.count)
{
// Refresh the receipt flag during this process
_hasReadReceipts = NO;
// Create attributed string
NSMutableAttributedString *currentAttributedTextMsg;
MXKRoomBubbleComponent *component = [bubbleComponents firstObject];
NSAttributedString *componentString = component.attributedTextMessage;
NSInteger selectedComponentIndex = self.selectedComponentIndex;
NSInteger lastMessageIndex = self.containsLastMessage ? self.mostRecentComponentIndex : NSNotFound;
// Check whether another component than the first one is selected
if (selectedComponentIndex != NSNotFound && selectedComponentIndex != 0)
{
// 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])
{
[currentAttributedTextMsg appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]];
}
for (NSInteger index = 1; index < bubbleComponents.count; index++)
{
[currentAttributedTextMsg appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]];
component = bubbleComponents[index];
componentString = component.attributedTextMessage;
// 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]];
}
}
attributedTextMessage = currentAttributedTextMsg;
}
}
return attributedTextMessage;
}
#pragma mark -
- (void)setContainsLastMessage:(BOOL)containsLastMessage
{
// Check whether there is something to do
if (_containsLastMessage || containsLastMessage)
{
// Update flag
_containsLastMessage = containsLastMessage;
// Recompute the text message layout
self.attributedTextMessage = nil;
}
}
- (void)setHasReadReceipts:(BOOL)hasReadReceipts
{
// Check whether there is something to do
if (_hasReadReceipts || hasReadReceipts)
{
// Update flag
_hasReadReceipts = hasReadReceipts;
// Recompute the text message layout
self.attributedTextMessage = nil;
}
}
- (void)setSelectedEventId:(NSString *)selectedEventId
{
// Check whether there is something to do
if (_selectedEventId || selectedEventId.length)
{
// Update flag
_selectedEventId = selectedEventId;
// Recompute the text message layout
self.attributedTextMessage = nil;
}
}
- (NSInteger)mostRecentComponentIndex
{
// Update the related component index
NSInteger mostRecentComponentIndex = NSNotFound;
NSArray *components = self.bubbleComponents;
NSInteger index = components.count;
while (index--)
{
MXKRoomBubbleComponent *component = components[index];
if (component.date)
{
mostRecentComponentIndex = index;
break;
}
}
return mostRecentComponentIndex;
}
- (NSInteger)selectedComponentIndex
{
// Update the related component index
NSInteger selectedComponentIndex = NSNotFound;
if (_selectedEventId)
{
NSArray *components = self.bubbleComponents;
NSInteger index = components.count;
while (index--)
{
MXKRoomBubbleComponent *component = components[index];
if ([component.event.eventId isEqualToString:_selectedEventId])
{
selectedComponentIndex = index;
break;
}
}
}
return selectedComponentIndex;
}
#pragma mark -
+ (NSAttributedString *)timestampVerticalWhitespace
{
@synchronized(self)
{
if (timestampVerticalWhitespace == nil)
{
timestampVerticalWhitespace = [[NSAttributedString alloc] initWithString:@"\n" attributes:@{NSForegroundColorAttributeName : [UIColor blackColor],
NSFontAttributeName: [UIFont systemFontOfSize:12]}];
}
}
return timestampVerticalWhitespace;
}
+ (NSAttributedString *)readReceiptVerticalWhitespace
{
@synchronized(self)
{
if (readReceiptVerticalWhitespace == nil)
{
readReceiptVerticalWhitespace = [[NSAttributedString alloc] initWithString:@"\n\n" attributes:@{NSForegroundColorAttributeName : [UIColor blackColor],
NSFontAttributeName: [UIFont systemFontOfSize:4]}];
}
}
return readReceiptVerticalWhitespace;
}
@end