219 lines
7.4 KiB
Objective-C
219 lines
7.4 KiB
Objective-C
/*
|
|
Copyright 2024 New Vector Ltd.
|
|
Copyright 2015 OpenMarket Ltd
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
#import "MXKRoomBubbleComponent.h"
|
|
|
|
#import "MXEvent+MatrixKit.h"
|
|
#import "MXKSwiftHeader.h"
|
|
#import <MatrixSDK/MatrixSDK.h>
|
|
|
|
@interface MXKRoomBubbleComponent ()
|
|
|
|
@property (nonatomic, readwrite) id<MXThreadProtocol> thread;
|
|
|
|
@end
|
|
|
|
@implementation MXKRoomBubbleComponent
|
|
|
|
- (instancetype)initWithEvent:(MXEvent*)event
|
|
roomState:(MXRoomState*)roomState
|
|
andLatestRoomState:(MXRoomState*)latestRoomState
|
|
eventFormatter:(MXKEventFormatter*)eventFormatter
|
|
session:(MXSession*)session;
|
|
{
|
|
if (self = [super init])
|
|
{
|
|
// Build text component related to this event
|
|
_eventFormatter = eventFormatter;
|
|
MXKEventFormatterError error;
|
|
|
|
NSAttributedString *eventString = [_eventFormatter attributedStringFromEvent:event
|
|
withRoomState:roomState
|
|
andLatestRoomState:latestRoomState
|
|
error:&error];
|
|
|
|
// Store the potential error
|
|
event.mxkEventFormatterError = error;
|
|
|
|
_textMessage = nil;
|
|
_attributedTextMessage = eventString;
|
|
|
|
// Set date time
|
|
if (event.originServerTs != kMXUndefinedTimestamp)
|
|
{
|
|
_date = [NSDate dateWithTimeIntervalSince1970:(double)event.originServerTs/1000];
|
|
}
|
|
else
|
|
{
|
|
_date = nil;
|
|
}
|
|
|
|
// Keep ref on event (used to handle the read marker, or a potential event redaction).
|
|
_event = event;
|
|
|
|
_displayFix = MXKRoomBubbleComponentDisplayFixNone;
|
|
|
|
NSString *format = event.content[@"format"];
|
|
if ([format isKindOfClass:[NSString class]] && [format isEqualToString:kMXRoomMessageFormatHTML])
|
|
{
|
|
NSString *formattedBody = (NSString*)event.content[@"formatted_body"];
|
|
if ([formattedBody isKindOfClass:[NSString class]] && [formattedBody containsString:@"<blockquote"])
|
|
{
|
|
_displayFix |= MXKRoomBubbleComponentDisplayFixHtmlBlockquote;
|
|
}
|
|
}
|
|
|
|
_encryptionDecoration = [self encryptionDecorationForEvent:event roomState:(MXRoomState*)roomState session:session];
|
|
|
|
[self updateLinkWithRoomState:roomState];
|
|
|
|
if (event.unsignedData.relations.thread)
|
|
{
|
|
self.thread = [[MXThreadModel alloc] initWithRootEvent:event
|
|
notificationCount:0
|
|
highlightCount:0];
|
|
}
|
|
else
|
|
{
|
|
self.thread = [session.threadingService threadWithId:event.eventId];
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)updateWithEvent:(MXEvent*)event
|
|
roomState:(MXRoomState*)roomState
|
|
andLatestRoomState:(MXRoomState*)latestRoomState
|
|
session:(MXSession*)session
|
|
{
|
|
// Report the new event
|
|
_event = event;
|
|
|
|
if (_event.isRedactedEvent)
|
|
{
|
|
// Do not use the live room state for redacted events as they occurred in the past
|
|
// Note: as we don't have valid room state in this case, userId will be used as display name
|
|
roomState = nil;
|
|
}
|
|
// Other calls to updateWithEvent are made to update the state of an event (ex: MXKEventStateSending to MXKEventStateDefault).
|
|
// They occur in live so we can use the room up-to-date state without making huge errors
|
|
|
|
_textMessage = nil;
|
|
|
|
MXKEventFormatterError error;
|
|
_attributedTextMessage = [_eventFormatter attributedStringFromEvent:event
|
|
withRoomState:roomState
|
|
andLatestRoomState:latestRoomState
|
|
error:&error];
|
|
|
|
_encryptionDecoration = [self encryptionDecorationForEvent:event roomState:roomState session:session];
|
|
|
|
[self updateLinkWithRoomState:roomState];
|
|
}
|
|
|
|
- (NSString *)textMessage
|
|
{
|
|
if (!_textMessage)
|
|
{
|
|
_textMessage = _attributedTextMessage.string;
|
|
}
|
|
return _textMessage;
|
|
}
|
|
|
|
- (void)updateLinkWithRoomState:(MXRoomState*)roomState
|
|
{
|
|
// Ensure link detection has been enabled
|
|
if (!MXKAppSettings.standardAppSettings.enableBubbleComponentLinkDetection)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Only detect links in unencrypted rooms, for un-redacted message events that are text, notice or emote.
|
|
// Specifically check the room's encryption state rather than the event's as outgoing events are always unencrypted initially.
|
|
if (roomState.isEncrypted || self.event.eventType != MXEventTypeRoomMessage || [self.event isRedactedEvent])
|
|
{
|
|
self.link = nil; // Ensure there's no link for a redacted event
|
|
return;
|
|
}
|
|
|
|
NSString *messageType = self.event.content[kMXMessageTypeKey];
|
|
|
|
if (!messageType || !([messageType isEqualToString:kMXMessageTypeText] || [messageType isEqualToString:kMXMessageTypeNotice] || [messageType isEqualToString:kMXMessageTypeEmote]))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Detect links in the attributed string which gets updated when the message is edited.
|
|
// Restrict detection to the unquoted string so links are only found in the sender's message.
|
|
NSString *body = [self.attributedTextMessage mxk_unquotedString];
|
|
NSURL *url = [body mxk_firstURLDetected];
|
|
|
|
if (!url)
|
|
{
|
|
self.link = nil;
|
|
return;
|
|
}
|
|
|
|
self.link = url;
|
|
}
|
|
|
|
- (EventEncryptionDecoration)encryptionDecorationForEvent:(MXEvent*)event roomState:(MXRoomState*)roomState session:(MXSession*)session
|
|
{
|
|
// Warning badges are unnecessary in unencrypted rooms
|
|
if (!roomState.isEncrypted)
|
|
{
|
|
return EventEncryptionDecorationNone;
|
|
}
|
|
|
|
// Not all events are encrypted (e.g. state/reactions/redactions) and we only have encrypted cell subclasses for messages and attachments.
|
|
if (event.eventType != MXEventTypeRoomMessage && !event.isMediaAttachment)
|
|
{
|
|
return EventEncryptionDecorationNone;
|
|
}
|
|
|
|
// Always show a warning badge if there was a decryption error.
|
|
if (event.decryptionError)
|
|
{
|
|
return EventEncryptionDecorationRed;
|
|
}
|
|
|
|
// Unencrypted message events should show a warning unless they're pending local echoes
|
|
if (!event.isEncrypted)
|
|
{
|
|
if (event.isLocalEvent
|
|
|| event.contentHasBeenEdited) // Local echo for an edit is clear but uses a true event id, the one of the edited event
|
|
{
|
|
return EventEncryptionDecorationNone;
|
|
}
|
|
|
|
return EventEncryptionDecorationRed;
|
|
}
|
|
|
|
// The encryption is in a good state.
|
|
// Only show a warning badge if there are decryption trust issues.
|
|
if (event.decryptionDecoration)
|
|
{
|
|
switch (event.decryptionDecoration.color)
|
|
{
|
|
case MXEventDecryptionDecorationColorNone:
|
|
return EventEncryptionDecorationNone;
|
|
case MXEventDecryptionDecorationColorGrey:
|
|
return EventEncryptionDecorationGrey;
|
|
case MXEventDecryptionDecorationColorRed:
|
|
return EventEncryptionDecorationRed;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return EventEncryptionDecorationNone;
|
|
}
|
|
}
|
|
|
|
@end
|
|
|