Merge branch 'develop' into flescio/7253-Add_mar_kas_unread_option_for_rooms

This commit is contained in:
Flavio Alescio
2023-01-25 10:28:37 +01:00
260 changed files with 3970 additions and 1504 deletions
@@ -148,6 +148,8 @@ static NSString *const kMXAppGroupID = @"group.org.matrix";
kMXEventTypeStringKeyVerificationDone,
kMXEventTypeStringPollStart,
kMXEventTypeStringPollStartMSC3381,
kMXEventTypeStringPollEnd,
kMXEventTypeStringPollEndMSC3381,
kMXEventTypeStringBeaconInfo,
kMXEventTypeStringBeaconInfoMSC3672
].mutableCopy;
@@ -181,6 +183,8 @@ static NSString *const kMXAppGroupID = @"group.org.matrix";
kMXEventTypeStringKeyVerificationDone,
kMXEventTypeStringPollStart,
kMXEventTypeStringPollStartMSC3381,
kMXEventTypeStringPollEnd,
kMXEventTypeStringPollEndMSC3381,
kMXEventTypeStringBeaconInfo,
kMXEventTypeStringBeaconInfoMSC3672
].mutableCopy;
@@ -572,6 +572,7 @@ extern NSString *const kMXKRoomDataSourceTimelineErrorErrorKey;
Once complete, this local echo will be replaced by the event saved by the homeserver.
@param audioFileLocalURL the local filesystem path of the audio file to send.
@param additionalContentParams (optional) the additional parameters to the content.
@param mimeType (optional) the mime type of the file. Defaults to `audio/ogg`
@param duration the length of the voice message in milliseconds
@param samples an array of floating point values normalized to [0, 1], boxed within NSNumbers
@@ -580,6 +581,7 @@ extern NSString *const kMXKRoomDataSourceTimelineErrorErrorKey;
@param failure A block object called when the operation fails.
*/
- (void)sendVoiceMessage:(NSURL *)audioFileLocalURL
additionalContentParams:(NSDictionary*)additionalContentParams
mimeType:mimeType
duration:(NSUInteger)duration
samples:(NSArray<NSNumber *> *)samples
@@ -1998,6 +1998,7 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) {
}
- (void)sendVoiceMessage:(NSURL *)audioFileLocalURL
additionalContentParams:(NSDictionary *)additionalContentParams
mimeType:mimeType
duration:(NSUInteger)duration
samples:(NSArray<NSNumber *> *)samples
@@ -2006,7 +2007,7 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) {
{
__block MXEvent *localEchoEvent = nil;
[_room sendVoiceMessage:audioFileLocalURL mimeType:mimeType duration:duration samples:samples threadId:self.threadId localEcho:&localEchoEvent success:success failure:failure keepActualFilename:YES];
[_room sendVoiceMessage:audioFileLocalURL additionalContentParams:additionalContentParams mimeType:mimeType duration:duration samples:samples threadId:self.threadId localEcho:&localEchoEvent success:success failure:failure keepActualFilename:YES];
if (localEchoEvent)
{
@@ -2185,10 +2186,20 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) {
[self removeEventWithEventId:eventId];
if (event.isVoiceMessage) {
// Voice message
NSNumber *duration = event.content[kMXMessageContentKeyExtensibleAudioMSC1767][kMXMessageContentKeyExtensibleAudioDuration];
NSArray<NSNumber *> *samples = event.content[kMXMessageContentKeyExtensibleAudioMSC1767][kMXMessageContentKeyExtensibleAudioWaveform];
[self sendVoiceMessage:localFileURL mimeType:mimetype duration:duration.doubleValue samples:samples success:success failure:failure];
// Additional content params in case it is a voicebroacast chunk
NSDictionary* additionalContentParams = nil;
if (event.content[kMXEventRelationRelatesToKey] != nil && event.content[VoiceBroadcastSettings.voiceBroadcastContentKeyChunkType] != nil) {
additionalContentParams = @{
kMXEventRelationRelatesToKey: event.content[kMXEventRelationRelatesToKey],
VoiceBroadcastSettings.voiceBroadcastContentKeyChunkType: event.content[VoiceBroadcastSettings.voiceBroadcastContentKeyChunkType]
};
}
[self sendVoiceMessage:localFileURL additionalContentParams:additionalContentParams mimeType:mimetype duration:duration.doubleValue samples:samples success:success failure:failure];
} else {
[self sendAudioFile:localFileURL mimeType:mimetype success:success failure:failure];
}
@@ -2881,10 +2892,21 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) {
return NO;
}
if (event.eventType == MXEventTypePollStart) {
if (event.isTimelinePollEvent) {
return YES;
}
// Specific case for voice broadcast event
if (event.eventType == MXEventTypeCustom &&
[event.type isEqualToString:VoiceBroadcastSettings.voiceBroadcastInfoContentKeyType]) {
// Ensures that we only support reactions for a start event
VoiceBroadcastInfo* voiceBroadcastInfo = [VoiceBroadcastInfo modelFromJSON: event.content];
if ([VoiceBroadcastInfo isStartedFor: voiceBroadcastInfo.state]) {
return YES;
}
}
BOOL isRoomMessage = (event.eventType == MXEventTypeRoomMessage);
if (!isRoomMessage) {
@@ -18,34 +18,38 @@ import Foundation
class MXKSendReplyEventStringLocalizer: NSObject, MXSendReplyEventStringLocalizerProtocol {
func senderSentAnImage() -> String {
return VectorL10n.messageReplyToSenderSentAnImage
VectorL10n.messageReplyToSenderSentAnImage
}
func senderSentAVideo() -> String {
return VectorL10n.messageReplyToSenderSentAVideo
VectorL10n.messageReplyToSenderSentAVideo
}
func senderSentAnAudioFile() -> String {
return VectorL10n.messageReplyToSenderSentAnAudioFile
VectorL10n.messageReplyToSenderSentAnAudioFile
}
func senderSentAVoiceMessage() -> String {
return VectorL10n.messageReplyToSenderSentAVoiceMessage
VectorL10n.messageReplyToSenderSentAVoiceMessage
}
func senderSentAFile() -> String {
return VectorL10n.messageReplyToSenderSentAFile
VectorL10n.messageReplyToSenderSentAFile
}
func senderSentTheirLocation() -> String {
return VectorL10n.messageReplyToSenderSentTheirLocation
VectorL10n.messageReplyToSenderSentTheirLocation
}
func senderSentTheirLiveLocation() -> String {
return VectorL10n.messageReplyToSenderSentTheirLiveLocation
VectorL10n.messageReplyToSenderSentTheirLiveLocation
}
func messageToReplyToPrefix() -> String {
return VectorL10n.messageReplyToMessageToReplyToPrefix
VectorL10n.messageReplyToMessageToReplyToPrefix
}
func replyToEndedPoll() -> String {
VectorL10n.pollTimelineReplyEndedPoll
}
}
@@ -51,6 +51,7 @@ class HTMLFormatter: NSObject {
DTDefaultFontName: font.fontName,
DTDefaultFontSize: font.pointSize,
DTDefaultLinkDecoration: false,
DTDefaultLinkColor: ThemeService.shared().theme.colors.links,
DTWillFlushBlockCallBack: sanitizeCallback
]
options.merge(extraOptions) { (_, new) in new }
@@ -366,6 +366,12 @@ typedef enum : NSUInteger {
*/
@property (nonatomic) UIColor *sendingTextColor;
/**
Color used to display links and hyperlinks contentt.
Default is [UIColor linkColor].
*/
@property (nonatomic) UIColor *linksColor;
/**
Color used to display error text.
Default is red.
@@ -31,6 +31,7 @@
#import "GeneratedInterface-Swift.h"
static NSString *const kHTMLATagRegexPattern = @"<a href=(?:'|\")(.*?)(?:'|\")>([^<]*)</a>";
static NSString *const kRepliedTextPattern = @"<mx-reply>.*<blockquote>.*<br>(.*)</blockquote></mx-reply>";
@interface MXKEventFormatter ()
{
@@ -89,6 +90,7 @@ static NSString *const kHTMLATagRegexPattern = @"<a href=(?:'|\")(.*?)(?:'|\")>(
_encryptingTextColor = [UIColor lightGrayColor];
_sendingTextColor = [UIColor lightGrayColor];
_errorTextColor = [UIColor redColor];
_linksColor = [UIColor linkColor];
_htmlBlockquoteBorderColor = [MXKTools colorWithRGBValue:0xDDDDDD];
_defaultTextFont = [UIFont systemFontOfSize:14];
@@ -1606,6 +1608,23 @@ static NSString *const kHTMLATagRegexPattern = @"<a href=(?:'|\")(.*?)(?:'|\")>(
}
break;
}
case MXEventTypePollEnd:
{
if (event.isEditEvent)
{
return nil;
}
MXEvent* pollStartedEvent = [self->mxSession.store eventWithEventId:event.relatesTo.eventId inRoom:event.roomId];
if (pollStartedEvent) {
displayText = [MXEventContentPollStart modelFromJSON:pollStartedEvent.content].question;
} else {
displayText = [VectorL10n pollTimelineEndedText];
}
break;
}
case MXEventTypePollStart:
{
if (event.isEditEvent)
@@ -1732,6 +1751,7 @@ static NSString *const kHTMLATagRegexPattern = @"<a href=(?:'|\")(.*?)(?:'|\")>(
if (url.URL)
{
[str addAttribute:NSLinkAttributeName value:url.URL range:matchRange];
[str addAttribute:NSForegroundColorAttributeName value:self.linksColor range:matchRange];
}
}
}
@@ -1789,6 +1809,7 @@ static NSString *const kHTMLATagRegexPattern = @"<a href=(?:'|\")(.*?)(?:'|\")>(
}
html = [self renderReplyTo:html withRoomState:roomState];
html = [self renderPollEndedReplyTo:html repliedEvent:repliedEvent];
}
// Apply the css style that corresponds to the event state
@@ -1861,6 +1882,12 @@ static NSString *const kHTMLATagRegexPattern = @"<a href=(?:'|\")(.*?)(?:'|\")>(
{
MXJSONModelSetString(repliedEventContent, repliedEvent.content[kMXMessageBodyKey]);
}
if (!repliedEventContent && repliedEvent.eventType == MXEventTypePollStart) {
repliedEventContent = [MXEventContentPollStart modelFromJSON:repliedEvent.content].question;
}
if (!repliedEventContent && repliedEvent.eventType == MXEventTypePollEnd) {
repliedEventContent = MXSendReplyEventDefaultStringLocalizer.new.replyToEndedPoll;
}
}
// No message content in a non-redacted event. Formatter should use fallback.
@@ -1983,8 +2010,8 @@ static NSString *const kHTMLATagRegexPattern = @"<a href=(?:'|\")(.*?)(?:'|\")>(
}
// Replace <mx-reply><blockquote><a href=\"__permalink__\">In reply to</a>
// By <mx-reply><blockquote><a href=\"#\">['In reply to' from resources]</a>
// To disable the link and to localize the "In reply to" string
// By <mx-reply><blockquote><a href=\"__permalink__\">['In reply to' from resources]</a>
// To localize the "In reply to" string
// This link is the first <a> HTML node of the html string
if (inReplyToTextRange.location != NSNotFound)
@@ -1992,12 +2019,45 @@ static NSString *const kHTMLATagRegexPattern = @"<a href=(?:'|\")(.*?)(?:'|\")>(
html = [html stringByReplacingCharactersInRange:inReplyToTextRange withString:[VectorL10n noticeInReplyTo]];
}
if (inReplyToLinkRange.location != NSNotFound)
{
html = [html stringByReplacingCharactersInRange:inReplyToLinkRange withString:@"#"];
return html;
}
- (NSString*)renderPollEndedReplyTo:(NSString*)htmlString repliedEvent:(MXEvent*)repliedEvent {
static NSRegularExpression *endedPollRegex;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
endedPollRegex = [NSRegularExpression regularExpressionWithPattern:kRepliedTextPattern options:NSRegularExpressionCaseInsensitive error:nil];
});
NSString* finalString = htmlString;
if (repliedEvent.eventType != MXEventTypePollEnd) {
return finalString;
}
return html;
NSTextCheckingResult* match = [endedPollRegex firstMatchInString:htmlString options:0 range:NSMakeRange(0, htmlString.length)];
if (!(match && match.numberOfRanges > 1)) {
// no useful match found
return finalString;
}
NSRange groupRange = [match rangeAtIndex:1];
NSString* replacementText;
if (repliedEvent) {
MXEvent* pollStartedEvent = [mxSession.store eventWithEventId:repliedEvent.relatesTo.eventId inRoom:repliedEvent.roomId];
replacementText = [MXEventContentPollStart modelFromJSON:pollStartedEvent.content].question;
}
if (replacementText == nil) {
replacementText = VectorL10n.pollTimelineReplyEndedPoll;
}
finalString = [htmlString stringByReplacingCharactersInRange:groupRange withString:replacementText];
return finalString;
}
- (void)postFormatMutableAttributedString:(NSMutableAttributedString*)mutableAttributedString
+23 -1
View File
@@ -46,6 +46,7 @@ static NSRegularExpression *eventIdRegex;
static NSRegularExpression *httpLinksRegex;
// A regex to find all HTML tags
static NSRegularExpression *htmlTagsRegex;
static NSDataDetector *linkDetector;
@implementation MXKTools
@@ -60,7 +61,8 @@ static NSRegularExpression *htmlTagsRegex;
eventIdRegex = [NSRegularExpression regularExpressionWithPattern:kMXToolsRegexStringForMatrixEventIdentifier options:NSRegularExpressionCaseInsensitive error:nil];
httpLinksRegex = [NSRegularExpression regularExpressionWithPattern:@"(?i)\\b(https?://\\S*)\\b" options:NSRegularExpressionCaseInsensitive error:nil];
htmlTagsRegex = [NSRegularExpression regularExpressionWithPattern:@"<(\\w+)[^>]*>" options:NSRegularExpressionCaseInsensitive error:nil];
htmlTagsRegex = [NSRegularExpression regularExpressionWithPattern:@"<(\\w+)[^>]*>" options:NSRegularExpressionCaseInsensitive error:nil];
linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
});
}
@@ -1037,6 +1039,23 @@ manualChangeMessageForVideo:(NSString*)manualChangeMessageForVideo
{
[MXKTools createLinksInMutableAttributedString:mutableAttributedString matchingRegex:eventIdRegex];
}
// This allows to check for normal url based links (like https://element.io)
// And set back the default link color
NSArray *matches = [linkDetector matchesInString: [mutableAttributedString string] options:0 range: NSMakeRange(0,mutableAttributedString.length)];
if (matches)
{
for (NSTextCheckingResult *match in matches)
{
NSRange matchRange = [match range];
NSURL *matchUrl = [match URL];
NSURLComponents *url = [[NSURLComponents new] initWithURL:matchUrl resolvingAgainstBaseURL:NO];
if (url.URL)
{
[mutableAttributedString addAttribute:NSForegroundColorAttributeName value:ThemeService.shared.theme.colors.links range:matchRange];
}
}
}
}
+ (void)createLinksInMutableAttributedString:(NSMutableAttributedString*)mutableAttributedString matchingRegex:(NSRegularExpression*)regex
@@ -1083,6 +1102,8 @@ manualChangeMessageForVideo:(NSString*)manualChangeMessageForVideo
// If the match is fully in the link, skip it
if (NSIntersectionRange(match.range, linkMatch.range).length == match.range.length)
{
// but before we set the right color
[mutableAttributedString addAttribute:NSForegroundColorAttributeName value:ThemeService.shared.theme.colors.links range:linkMatch.range];
hasAlreadyLink = YES;
break;
}
@@ -1097,6 +1118,7 @@ manualChangeMessageForVideo:(NSString*)manualChangeMessageForVideo
NSString *link = [mutableAttributedString.string substringWithRange:match.range];
link = [link stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
[mutableAttributedString addAttribute:NSLinkAttributeName value:link range:match.range];
[mutableAttributedString addAttribute:NSForegroundColorAttributeName value:ThemeService.shared.theme.colors.links range:match.range];
}
}];
}
@@ -67,6 +67,7 @@
- (void)setAttributedText:(NSAttributedString *)attributedText
{
self.linkTextAttributes = @{};
if (@available(iOS 15.0, *)) {
[self flushPills];
}