зеркало из https://github.com/nextcloud/talk-ios.git
790 строки
30 KiB
Objective-C
790 строки
30 KiB
Objective-C
/**
|
|
* @copyright Copyright (c) 2020 Ivan Sein <ivan@nextcloud.com>
|
|
*
|
|
* @author Ivan Sein <ivan@nextcloud.com>
|
|
*
|
|
* @license GNU GPL version 3 or any later version
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#import "NCChatMessage.h"
|
|
|
|
#import "NCAPIController.h"
|
|
#import "NCAppBranding.h"
|
|
#import "NextcloudTalk-Swift.h"
|
|
|
|
NSInteger const kChatMessageGroupTimeDifference = 30;
|
|
|
|
NSString * const kMessageTypeComment = @"comment";
|
|
NSString * const kMessageTypeCommentDeleted = @"comment_deleted";
|
|
NSString * const kMessageTypeSystem = @"system";
|
|
NSString * const kMessageTypeCommand = @"command";
|
|
NSString * const kMessageTypeVoiceMessage = @"voice-message";
|
|
|
|
NSString * const kSharedItemTypeAudio = @"audio";
|
|
NSString * const kSharedItemTypeDeckcard = @"deckcard";
|
|
NSString * const kSharedItemTypeFile = @"file";
|
|
NSString * const kSharedItemTypeLocation = @"location";
|
|
NSString * const kSharedItemTypeMedia = @"media";
|
|
NSString * const kSharedItemTypeOther = @"other";
|
|
NSString * const kSharedItemTypeVoice = @"voice";
|
|
NSString * const kSharedItemTypePoll = @"poll";
|
|
NSString * const kSharedItemTypeRecording = @"recording";
|
|
|
|
@interface NCChatMessage ()
|
|
{
|
|
NCMessageFileParameter *_fileParameter;
|
|
NCMessageLocationParameter *_locationParameter;
|
|
NCDeckCardParameter *_deckCardParameter;
|
|
NSString *_objectShareLink;
|
|
NSMutableArray *_temporaryReactions;
|
|
BOOL _urlDetectionDone;
|
|
NSString *_urlDetected;
|
|
BOOL _referenceDataDone;
|
|
NSDictionary *_referenceData;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation NCChatMessage
|
|
|
|
+ (instancetype)messageWithDictionary:(NSDictionary *)messageDict
|
|
{
|
|
if (!messageDict || ![messageDict isKindOfClass:[NSDictionary class]]) {
|
|
return nil;
|
|
}
|
|
|
|
NCChatMessage *message = [[NCChatMessage alloc] init];
|
|
message.actorId = [messageDict objectForKey:@"actorId"];
|
|
message.actorType = [messageDict objectForKey:@"actorType"];
|
|
message.messageId = [[messageDict objectForKey:@"id"] integerValue];
|
|
message.message = [messageDict objectForKey:@"message"];
|
|
message.timestamp = [[messageDict objectForKey:@"timestamp"] integerValue];
|
|
message.token = [messageDict objectForKey:@"token"];
|
|
message.systemMessage = [messageDict objectForKey:@"systemMessage"];
|
|
message.isReplyable = [[messageDict objectForKey:@"isReplyable"] boolValue];
|
|
message.referenceId = [messageDict objectForKey:@"referenceId"];
|
|
message.messageType = [messageDict objectForKey:@"messageType"];
|
|
message.expirationTimestamp = [[messageDict objectForKey:@"expirationTimestamp"] integerValue];
|
|
|
|
id actorDisplayName = [messageDict objectForKey:@"actorDisplayName"];
|
|
if (!actorDisplayName) {
|
|
message.actorDisplayName = @"";
|
|
} else {
|
|
if ([actorDisplayName isKindOfClass:[NSString class]]) {
|
|
message.actorDisplayName = actorDisplayName;
|
|
} else {
|
|
message.actorDisplayName = [actorDisplayName stringValue];
|
|
}
|
|
}
|
|
|
|
id messageParameters = [messageDict objectForKey:@"messageParameters"];
|
|
if ([messageParameters isKindOfClass:[NSDictionary class]]) {
|
|
NSError *error;
|
|
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:messageParameters
|
|
options:0
|
|
error:&error];
|
|
if (jsonData) {
|
|
message.messageParametersJSONString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
|
} else {
|
|
NSLog(@"Error generating message parameters JSON string: %@", error);
|
|
}
|
|
}
|
|
|
|
id reactions = [messageDict objectForKey:@"reactions"];
|
|
if ([reactions isKindOfClass:[NSDictionary class]]) {
|
|
NSError *error;
|
|
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:reactions
|
|
options:0
|
|
error:&error];
|
|
if (jsonData) {
|
|
message.reactionsJSONString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
|
} else {
|
|
NSLog(@"Error generating reactions JSON string: %@", error);
|
|
}
|
|
}
|
|
|
|
id reactionsSelf = [messageDict objectForKey:@"reactionsSelf"];
|
|
if ([reactionsSelf isKindOfClass:[NSArray class]]) {
|
|
NSError *error;
|
|
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:reactionsSelf
|
|
options:0
|
|
error:&error];
|
|
if (jsonData) {
|
|
message.reactionsSelfJSONString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
|
} else {
|
|
NSLog(@"Error generating reactionsSelf JSON string: %@", error);
|
|
}
|
|
}
|
|
|
|
return message;
|
|
}
|
|
|
|
+ (instancetype)messageWithDictionary:(NSDictionary *)messageDict andAccountId:(NSString *)accountId
|
|
{
|
|
NCChatMessage *message = [NCChatMessage messageWithDictionary:messageDict];
|
|
if (message) {
|
|
message.accountId = accountId;
|
|
message.internalId = [NSString stringWithFormat:@"%@@%@@%ld", accountId, message.token, (long)message.messageId];
|
|
}
|
|
|
|
return message;
|
|
}
|
|
|
|
+ (void)updateChatMessage:(NCChatMessage *)managedChatMessage withChatMessage:(NCChatMessage *)chatMessage isRoomLastMessage:(BOOL)isRoomLastMessage
|
|
{
|
|
managedChatMessage.actorDisplayName = chatMessage.actorDisplayName;
|
|
managedChatMessage.actorId = chatMessage.actorId;
|
|
managedChatMessage.actorType = chatMessage.actorType;
|
|
managedChatMessage.message = chatMessage.message;
|
|
managedChatMessage.messageParametersJSONString = chatMessage.messageParametersJSONString;
|
|
managedChatMessage.timestamp = chatMessage.timestamp;
|
|
managedChatMessage.systemMessage = chatMessage.systemMessage;
|
|
managedChatMessage.isReplyable = chatMessage.isReplyable;
|
|
managedChatMessage.messageType = chatMessage.messageType;
|
|
managedChatMessage.reactionsJSONString = chatMessage.reactionsJSONString;
|
|
managedChatMessage.expirationTimestamp = chatMessage.expirationTimestamp;
|
|
|
|
if (!isRoomLastMessage) {
|
|
managedChatMessage.reactionsSelfJSONString = chatMessage.reactionsSelfJSONString;
|
|
}
|
|
|
|
if (!managedChatMessage.parentId && chatMessage.parentId) {
|
|
managedChatMessage.parentId = chatMessage.parentId;
|
|
}
|
|
}
|
|
|
|
+ (NSString *)primaryKey {
|
|
return @"internalId";
|
|
}
|
|
|
|
- (id)copyWithZone:(NSZone *)zone
|
|
{
|
|
NCChatMessage *messageCopy = [[NCChatMessage alloc] init];
|
|
|
|
messageCopy.internalId = [_internalId copyWithZone:zone];
|
|
messageCopy.accountId = [_accountId copyWithZone:zone];
|
|
messageCopy.actorDisplayName = [_actorDisplayName copyWithZone:zone];
|
|
messageCopy.actorId = [_actorId copyWithZone:zone];
|
|
messageCopy.actorType = [_actorType copyWithZone:zone];
|
|
messageCopy.messageId = _messageId;
|
|
messageCopy.message = [_message copyWithZone:zone];
|
|
messageCopy.messageParametersJSONString = [_messageParametersJSONString copyWithZone:zone];
|
|
messageCopy.timestamp = _timestamp;
|
|
messageCopy.token = [_token copyWithZone:zone];
|
|
messageCopy.systemMessage = [_systemMessage copyWithZone:zone];
|
|
messageCopy.isReplyable = _isReplyable;
|
|
messageCopy.parentId = [_parentId copyWithZone:zone];
|
|
messageCopy.referenceId = [_referenceId copyWithZone:zone];
|
|
messageCopy.messageType = [_messageType copyWithZone:zone];
|
|
messageCopy.reactionsJSONString = [_reactionsJSONString copyWithZone:zone];
|
|
messageCopy.reactionsSelfJSONString = [_reactionsSelfJSONString copyWithZone:zone];
|
|
messageCopy.expirationTimestamp = _expirationTimestamp;
|
|
messageCopy.isTemporary = _isTemporary;
|
|
messageCopy.sendingFailed = _sendingFailed;
|
|
messageCopy.isGroupMessage = _isGroupMessage;
|
|
messageCopy.isDeleting = _isDeleting;
|
|
messageCopy.isOfflineMessage = _isOfflineMessage;
|
|
messageCopy.isSilent = _isSilent;
|
|
|
|
return messageCopy;
|
|
}
|
|
|
|
- (BOOL)isSystemMessage
|
|
{
|
|
if (self.systemMessage && ![self.systemMessage isEqualToString:@""]) {
|
|
return YES;
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)isEmojiMessage
|
|
{
|
|
if (self.message && self.message.containsOnlyEmoji && self.message.emojiCount <= 3) {
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)isUpdateMessage
|
|
{
|
|
return [self.systemMessage isEqualToString:@"message_deleted"] ||
|
|
[self.systemMessage isEqualToString:@"reaction"] ||
|
|
[self.systemMessage isEqualToString:@"reaction_revoked"] ||
|
|
[self.systemMessage isEqualToString:@"reaction_deleted"] ||
|
|
[self.systemMessage isEqualToString:@"poll_voted"];
|
|
}
|
|
|
|
- (BOOL)isDeletedMessage
|
|
{
|
|
return [_messageType isEqualToString:kMessageTypeCommentDeleted];
|
|
}
|
|
|
|
- (BOOL)isVoiceMessage
|
|
{
|
|
return [_messageType isEqualToString:kMessageTypeVoiceMessage];
|
|
}
|
|
|
|
- (BOOL)isCommandMessage
|
|
{
|
|
return [_messageType isEqualToString:kMessageTypeCommand];
|
|
}
|
|
|
|
- (BOOL)isMessageFromUser:(NSString *)userId
|
|
{
|
|
return [self.actorId isEqualToString:userId] && [self.actorType isEqualToString:@"users"];
|
|
}
|
|
|
|
- (BOOL)isDeletableForAccount:(TalkAccount *)account andParticipantType:(NCParticipantType)participantType
|
|
{
|
|
NSInteger sixHoursAgoTimestamp = [[NSDate date] timeIntervalSince1970] - (6 * 3600);
|
|
|
|
BOOL severCanDeleteMessage =
|
|
// Delete normal messages
|
|
([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityDeleteMessages forAccountId:account.accountId] && [self.messageType isEqualToString:kMessageTypeComment] && !self.file && ![self isObjectShare]) ||
|
|
// Delete files or shared objects
|
|
([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityRichObjectDelete forAccountId:account.accountId] && (self.file || [self isVoiceMessage] ||[self isObjectShare]));
|
|
|
|
BOOL userCanDeleteMessage = (participantType == kNCParticipantTypeOwner || participantType == kNCParticipantTypeModerator || [self isMessageFromUser:account.userId]);
|
|
|
|
if (severCanDeleteMessage && userCanDeleteMessage && !self.isDeleting && self.timestamp >= sixHoursAgoTimestamp) {
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)isObjectShare
|
|
{
|
|
if ([self.message isEqualToString:@"{object}"] && [self.messageParameters objectForKey:@"object"]) {
|
|
return YES;
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (NSDictionary *)richObjectFromObjectShare
|
|
{
|
|
NSDictionary *richObjectDict = @{};
|
|
if ([self isObjectShare]) {
|
|
NSDictionary *objectDict = [self.messageParameters objectForKey:@"object"];
|
|
NSError *error;
|
|
NSString *jsonString = @"";
|
|
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:objectDict
|
|
options:0
|
|
error:&error];
|
|
if (!jsonData) {
|
|
NSLog(@"Got an error: %@", error);
|
|
} else {
|
|
jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
|
}
|
|
NCMessageParameter *parameter = [[NCMessageParameter alloc] initWithDictionary:objectDict];
|
|
richObjectDict = @{@"objectType": parameter.type,
|
|
@"objectId": parameter.parameterId,
|
|
@"metaData": jsonString};
|
|
}
|
|
return richObjectDict;
|
|
}
|
|
|
|
- (NCMessageParameter *)file
|
|
{
|
|
if (!_fileParameter) {
|
|
for (NSDictionary *parameterDict in [[self messageParameters] allValues]) {
|
|
NCMessageFileParameter *parameter = [[NCMessageFileParameter alloc] initWithDictionary:parameterDict];
|
|
if (![parameter.type isEqualToString:@"file"]) {
|
|
continue;
|
|
}
|
|
|
|
if (!_fileParameter) {
|
|
_fileParameter = parameter;
|
|
} else {
|
|
// If there is more than one file in the message,
|
|
// we don't display any preview.
|
|
_fileParameter = nil;
|
|
return nil;
|
|
}
|
|
}
|
|
}
|
|
|
|
return _fileParameter;
|
|
}
|
|
|
|
- (NCMessageLocationParameter *)geoLocation
|
|
{
|
|
if (!_locationParameter) {
|
|
for (NSDictionary *parameterDict in [[self messageParameters] allValues]) {
|
|
NCMessageLocationParameter *parameter = [[NCMessageLocationParameter alloc] initWithDictionary:parameterDict] ;
|
|
if ([parameter.type isEqualToString:@"geo-location"]) {
|
|
_locationParameter = parameter;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return _locationParameter;
|
|
}
|
|
|
|
- (NCDeckCardParameter *)deckCard
|
|
{
|
|
if (!_deckCardParameter) {
|
|
for (NSDictionary *parameterDict in [[self messageParameters] allValues]) {
|
|
NCDeckCardParameter *parameter = [[NCDeckCardParameter alloc] initWithDictionary:parameterDict] ;
|
|
if ([parameter.type isEqualToString:@"deck-card"]) {
|
|
_deckCardParameter = parameter;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return _deckCardParameter;
|
|
}
|
|
|
|
- (NCMessageParameter *)poll
|
|
{
|
|
if ([self isObjectShare]) {
|
|
NCMessageParameter *objectParameter = [[NCMessageParameter alloc] initWithDictionary:[self.messageParameters objectForKey:@"object"]];
|
|
if ([objectParameter.type isEqualToString:@"talk-poll"]) {
|
|
return objectParameter;
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (NCMessageParameter *)objectShareParameter
|
|
{
|
|
if ([self isObjectShare]) {
|
|
NCMessageParameter *objectParameter = [[NCMessageParameter alloc] initWithDictionary:[self.messageParameters objectForKey:@"object"]];
|
|
return objectParameter;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (NSString *)objectShareLink;
|
|
{
|
|
if (!_objectShareLink && [self isObjectShare]) {
|
|
_objectShareLink = [[self.messageParameters objectForKey:@"object"] objectForKey:@"link"];
|
|
}
|
|
|
|
return _objectShareLink;
|
|
}
|
|
|
|
- (NSDictionary *)messageParameters
|
|
{
|
|
NSDictionary *messageParametersDict = @{};
|
|
NSData *data = [self.messageParametersJSONString dataUsingEncoding:NSUTF8StringEncoding];
|
|
if (data) {
|
|
NSError* error;
|
|
NSDictionary* jsonData = [NSJSONSerialization JSONObjectWithData:data
|
|
options:0
|
|
error:&error];
|
|
if (jsonData) {
|
|
messageParametersDict = jsonData;
|
|
} else {
|
|
NSLog(@"Error retrieving message parameters JSON data: %@", error);
|
|
}
|
|
}
|
|
return messageParametersDict;
|
|
}
|
|
|
|
- (NSMutableAttributedString *)parsedMessage
|
|
{
|
|
if (!self.message) {
|
|
return nil;
|
|
}
|
|
|
|
NSString *originalMessage = self.file.contactName ? self.file.contactName : self.message;
|
|
NSString *parsedMessage = originalMessage;
|
|
NSError *error = nil;
|
|
|
|
NSRegularExpression *parameterRegex = [NSRegularExpression regularExpressionWithPattern:@"\\{([^}]+)\\}" options:NSRegularExpressionCaseInsensitive error:&error];
|
|
NSArray *matches = [parameterRegex matchesInString:originalMessage
|
|
options:0
|
|
range:NSMakeRange(0, [originalMessage length])];
|
|
|
|
// Find message parameters
|
|
NSMutableArray *parameters = [NSMutableArray new];
|
|
for (NSTextCheckingResult *match in matches) {
|
|
NSString* parameter = [originalMessage substringWithRange:match.range];
|
|
NSString *parameterKey = [[parameter stringByReplacingOccurrencesOfString:@"{" withString:@""]
|
|
stringByReplacingOccurrencesOfString:@"}" withString:@""];
|
|
NSDictionary *parameterDict = [[self messageParameters] objectForKey:parameterKey];
|
|
if (parameterDict) {
|
|
NCMessageParameter *messageParameter = [[NCMessageParameter alloc] initWithDictionary:parameterDict] ;
|
|
// Default replacement string is the parameter name
|
|
NSString *replaceString = messageParameter.name;
|
|
// Format user and call mentions
|
|
if ([messageParameter.type isEqualToString:@"user"] || [messageParameter.type isEqualToString:@"guest"] || [messageParameter.type isEqualToString:@"call"]) {
|
|
replaceString = [NSString stringWithFormat:@"@%@", [parameterDict objectForKey:@"name"]];
|
|
}
|
|
parsedMessage = [parsedMessage stringByReplacingOccurrencesOfString:parameter withString:replaceString];
|
|
// Calculate parameter range
|
|
NSRange searchRange = NSMakeRange(0,parsedMessage.length);
|
|
if (parameters.count > 0) {
|
|
NCMessageParameter *lastParameter = [parameters objectAtIndex:parameters.count - 1];
|
|
NSInteger newRangeLocation = lastParameter.range.location + lastParameter.range.length;
|
|
searchRange = NSMakeRange(newRangeLocation, parsedMessage.length - newRangeLocation);
|
|
}
|
|
messageParameter.range = [parsedMessage rangeOfString:replaceString options:0 range:searchRange];
|
|
[parameters addObject:messageParameter];
|
|
}
|
|
}
|
|
|
|
UIColor *defaultColor = [NCAppBranding chatForegroundColor];
|
|
UIColor *highlightedColor = [NCAppBranding elementColor];
|
|
|
|
NSMutableAttributedString *attributedMessage = [[NSMutableAttributedString alloc] initWithString:parsedMessage];
|
|
[attributedMessage addAttribute:NSForegroundColorAttributeName value:defaultColor range:NSMakeRange(0,parsedMessage.length)];
|
|
|
|
if (self.isEmojiMessage) {
|
|
[attributedMessage addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:36.0f] range:NSMakeRange(0,parsedMessage.length)];
|
|
} else {
|
|
[attributedMessage addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:16.0f] range:NSMakeRange(0,parsedMessage.length)];
|
|
}
|
|
|
|
for (NCMessageParameter *param in parameters) {
|
|
//Set color for mentions
|
|
if ([param.type isEqualToString:@"user"] || [param.type isEqualToString:@"guest"] || [param.type isEqualToString:@"call"]) {
|
|
[attributedMessage addAttribute:NSForegroundColorAttributeName value:(param.shouldBeHighlighted) ? highlightedColor : defaultColor range:param.range];
|
|
[attributedMessage addAttribute:NSFontAttributeName value:[UIFont boldSystemFontOfSize:16.0f] range:param.range];
|
|
}
|
|
//Create a link if parameter contains a link
|
|
else if (param.link) {
|
|
// Do not create links for files. File preview images will redirect to files client or browser.
|
|
if ([param.type isEqualToString:@"file"]) {
|
|
[attributedMessage addAttribute:NSFontAttributeName value:[UIFont boldSystemFontOfSize:16.0f] range:param.range];
|
|
} else {
|
|
[attributedMessage addAttribute:NSLinkAttributeName value:param.link range:param.range];
|
|
}
|
|
}
|
|
}
|
|
|
|
return attributedMessage;
|
|
}
|
|
|
|
- (NSMutableAttributedString *)parsedMessageForChat
|
|
{
|
|
// In some circumstances we want/need to hide the message in the chat, but still want to show it in other parts like the conversation list
|
|
if ([self getDeckCardUrlForReferenceProvider]) {
|
|
return nil;
|
|
}
|
|
|
|
return self.parsedMessage;
|
|
}
|
|
|
|
- (NSMutableAttributedString *)systemMessageFormat
|
|
{
|
|
NSMutableAttributedString *message = [self parsedMessage];
|
|
|
|
//TODO: Further adjust for dark-mode ?
|
|
[message addAttribute:NSForegroundColorAttributeName value:[UIColor tertiaryLabelColor] range:NSMakeRange(0,message.length)];
|
|
|
|
return message;
|
|
}
|
|
|
|
- (NSString *)sendingMessage
|
|
{
|
|
NSString *resultMessage = [[self.message copy] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
|
|
for (NSString *parameterKey in self.messageParameters.allKeys) {
|
|
NCMessageParameter *parameter = [[NCMessageParameter alloc] initWithDictionary:[self.messageParameters objectForKey:parameterKey]];
|
|
NSString *parameterKeyString = [[NSString alloc] initWithFormat:@"{%@}", parameterKey];
|
|
resultMessage = [resultMessage stringByReplacingOccurrencesOfString:parameterKeyString withString:parameter.mentionId];
|
|
}
|
|
|
|
return resultMessage;
|
|
}
|
|
|
|
- (NCChatMessage *)parent
|
|
{
|
|
if (self.parentId) {
|
|
NCChatMessage *unmanagedChatMessage = nil;
|
|
NCChatMessage *managedChatMessage = [NCChatMessage objectsWhere:@"internalId = %@", self.parentId].firstObject;
|
|
if (managedChatMessage) {
|
|
unmanagedChatMessage = [[NCChatMessage alloc] initWithValue:managedChatMessage];
|
|
}
|
|
return unmanagedChatMessage;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (NSInteger)parentMessageId
|
|
{
|
|
NSInteger messageId = self.parent ? self.parent.messageId : -1;
|
|
return messageId;
|
|
}
|
|
|
|
- (NSMutableArray *)temporaryReactions
|
|
{
|
|
if (!_temporaryReactions) {
|
|
_temporaryReactions = [NSMutableArray new];
|
|
}
|
|
return _temporaryReactions;
|
|
}
|
|
|
|
- (BOOL)isReactionBeingModified:(NSString *)reaction
|
|
{
|
|
for (NCChatReaction *temporaryReaction in [self temporaryReactions]) {
|
|
if ([temporaryReaction.reaction isEqualToString:reaction]) {
|
|
return YES;
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (void)removeReactionFromTemporayReactions:(NSString *)reaction
|
|
{
|
|
NCChatReaction *removeReaction = nil;
|
|
for (NCChatReaction *temporaryReaction in [self temporaryReactions]) {
|
|
if ([temporaryReaction.reaction isEqualToString:reaction]) {
|
|
removeReaction = temporaryReaction;
|
|
break;
|
|
}
|
|
}
|
|
if (removeReaction) {
|
|
[[self temporaryReactions] removeObject:removeReaction];
|
|
}
|
|
}
|
|
|
|
- (void)addTemporaryReaction:(NSString *)reaction
|
|
{
|
|
NCChatReaction *temporaryReaction = [[NCChatReaction alloc] init];
|
|
temporaryReaction.reaction = reaction;
|
|
temporaryReaction.state = NCChatReactionStateAdding;
|
|
[[self temporaryReactions] addObject:temporaryReaction];
|
|
}
|
|
|
|
- (void)removeReactionTemporarily:(NSString *)reaction
|
|
{
|
|
NCChatReaction *temporaryReaction = [[NCChatReaction alloc] init];
|
|
temporaryReaction.reaction = reaction;
|
|
temporaryReaction.state = NCChatReactionStateRemoving;
|
|
[[self temporaryReactions] addObject:temporaryReaction];
|
|
}
|
|
|
|
- (void)mergeTemporaryReactionsWithReactions:(NSMutableArray *)reactions
|
|
{
|
|
for (NCChatReaction *temporaryReaction in [self temporaryReactions]) {
|
|
if (temporaryReaction.state == NCChatReactionStateAdding) {
|
|
[self addTemporaryReaction:temporaryReaction.reaction inReactions:reactions];
|
|
} else if (temporaryReaction.state == NCChatReactionStateRemoving) {
|
|
[self removeReactionTemporarily:temporaryReaction.reaction inReactions:reactions];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)addTemporaryReaction:(NSString *)reaction inReactions:(NSMutableArray *)reactions
|
|
{
|
|
BOOL includedReaction = NO;
|
|
for (NCChatReaction *currentReaction in reactions) {
|
|
if ([currentReaction.reaction isEqualToString:reaction]) {
|
|
currentReaction.count += 1;
|
|
currentReaction.userReacted = YES;
|
|
includedReaction = YES;
|
|
}
|
|
}
|
|
if (!includedReaction) {
|
|
NCChatReaction *newReaction = [[NCChatReaction alloc] init];
|
|
newReaction.reaction = reaction;
|
|
newReaction.count = 1;
|
|
newReaction.userReacted = YES;
|
|
[reactions addObject:newReaction];
|
|
}
|
|
}
|
|
|
|
- (void)removeReactionTemporarily:(NSString *)reaction inReactions:(NSMutableArray *)reactions
|
|
{
|
|
NCChatReaction *removeReaction = nil;
|
|
for (NCChatReaction *currentReaction in reactions) {
|
|
if ([currentReaction.reaction isEqualToString:reaction]) {
|
|
currentReaction.state = NCChatReactionStateRemoving;
|
|
if (currentReaction.count > 1) {
|
|
currentReaction.count -= 1;
|
|
currentReaction.userReacted = NO;
|
|
} else {
|
|
removeReaction = currentReaction;
|
|
}
|
|
}
|
|
}
|
|
if (removeReaction) {
|
|
[reactions removeObject:removeReaction];
|
|
}
|
|
}
|
|
|
|
- (NSDictionary *)reactionsDictionary
|
|
{
|
|
NSDictionary *reactionsDictionary = @{};
|
|
NSData *data = [self.reactionsJSONString dataUsingEncoding:NSUTF8StringEncoding];
|
|
if (data) {
|
|
NSError* error;
|
|
NSDictionary* jsonData = [NSJSONSerialization JSONObjectWithData:data
|
|
options:0
|
|
error:&error];
|
|
if (jsonData) {
|
|
reactionsDictionary = jsonData;
|
|
} else {
|
|
NSLog(@"Error retrieving reactions JSON data: %@", error);
|
|
}
|
|
}
|
|
return reactionsDictionary;
|
|
}
|
|
|
|
- (NSArray *)reactionsSelfArray
|
|
{
|
|
NSArray *reactionsSelfArray = @[];
|
|
NSData *data = [self.reactionsSelfJSONString dataUsingEncoding:NSUTF8StringEncoding];
|
|
if (data) {
|
|
NSError* error;
|
|
NSArray* jsonData = [NSJSONSerialization JSONObjectWithData:data
|
|
options:0
|
|
error:&error];
|
|
if (jsonData) {
|
|
reactionsSelfArray = jsonData;
|
|
} else {
|
|
NSLog(@"Error retrieving reactionsSelf JSON data: %@", error);
|
|
}
|
|
}
|
|
return reactionsSelfArray;
|
|
}
|
|
|
|
- (NSMutableArray *)reactionsArray
|
|
{
|
|
NSMutableArray *reactionsArray = [NSMutableArray new];
|
|
// Grab message reactions
|
|
NSDictionary *reactionsDict = [self reactionsDictionary];
|
|
for (NSString *reactionKey in reactionsDict.allKeys) {
|
|
// We need to keep this check for users who installed v14.0 (beta 1)
|
|
if ([reactionKey isEqualToString:@"self"]) {continue;}
|
|
NCChatReaction *reaction = [NCChatReaction initWithReaction:reactionKey andCount:[[reactionsDict objectForKey:reactionKey] integerValue]];
|
|
[reactionsArray addObject:reaction];
|
|
}
|
|
// Set flag for own reactions
|
|
for (NSString *ownReaction in [self reactionsSelfArray]) {
|
|
for (NCChatReaction *reaction in reactionsArray) {
|
|
if ([reaction.reaction isEqualToString:ownReaction]) {
|
|
reaction.userReacted = YES;
|
|
}
|
|
}
|
|
}
|
|
// Merge with temporary reactions
|
|
[self mergeTemporaryReactionsWithReactions:reactionsArray];
|
|
// Sort by reactions count
|
|
NSSortDescriptor *valueDescriptor = [[NSSortDescriptor alloc] initWithKey:@"count" ascending:NO];
|
|
NSArray *descriptors = [NSArray arrayWithObject:valueDescriptor];
|
|
[reactionsArray sortUsingDescriptors:descriptors];
|
|
return reactionsArray;
|
|
}
|
|
|
|
- (BOOL)isReferenceApiSupported
|
|
{
|
|
// Check capabilities directly, otherwise NCSettingsController introduces new dependencies in NotificationServiceExtension
|
|
TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
|
|
ServerCapabilities *serverCapabilities = [[NCDatabaseManager sharedInstance] serverCapabilitiesForAccountId:activeAccount.accountId];
|
|
if (serverCapabilities) {
|
|
return serverCapabilities.referenceApiSupported;
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (NSString *)getDeckCardUrlForReferenceProvider
|
|
{
|
|
// Check if the message is a shared deck card and a reference provider can be used to retrieve details
|
|
if (self.deckCard != nil && self.deckCard.link != nil && [self.deckCard.link length] > 0) {
|
|
if ([self isReferenceApiSupported]) {
|
|
return _deckCardParameter.link;
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (BOOL)containsURL
|
|
{
|
|
if (_urlDetectionDone) {
|
|
return ([_urlDetected length] != 0);
|
|
}
|
|
|
|
if (![self isReferenceApiSupported]) {
|
|
_urlDetectionDone = YES;
|
|
return NO;
|
|
}
|
|
|
|
NSString *deckCardUrl = [self getDeckCardUrlForReferenceProvider];
|
|
|
|
if (deckCardUrl != nil) {
|
|
_urlDetectionDone = YES;
|
|
_urlDetected = deckCardUrl;
|
|
return YES;
|
|
}
|
|
|
|
NSDataDetector *dataDetector = [[NSDataDetector alloc] initWithTypes:NSTextCheckingTypeLink error:nil];
|
|
NSArray *urlMatches = [dataDetector matchesInString:self.message options:0 range:NSMakeRange(0, [self.message length])];
|
|
|
|
_urlDetectionDone = YES;
|
|
|
|
for (NSTextCheckingResult *match in urlMatches) {
|
|
NSURL *url = [match URL];
|
|
NSString *scheme = [url scheme];
|
|
|
|
// Check that the scheme is either https or http, because other schemes (like mailto) would be recognized as well
|
|
if ([[scheme lowercaseString] isEqualToString:@"http"] || [[scheme lowercaseString] isEqualToString:@"https"]) {
|
|
_urlDetected = [url absoluteString];
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
- (void)getReferenceDataWithCompletionBlock:(GetReferenceDataCompletionBlock)block
|
|
{
|
|
if (_referenceDataDone) {
|
|
if (block) {
|
|
block(self, _referenceData, _urlDetected);
|
|
}
|
|
} else {
|
|
TalkAccount *account = [[NCDatabaseManager sharedInstance] talkAccountForAccountId:_accountId];
|
|
|
|
[[NCAPIController sharedInstance] getReferenceForUrlString:_urlDetected forAccount:account withCompletionBlock:^(NSDictionary *references, NSError *error) {
|
|
if (block) {
|
|
block(self, references, self->_urlDetected);
|
|
}
|
|
|
|
self->_referenceData = references;
|
|
self->_referenceDataDone = YES;
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (BOOL)isSameMessage:(NCChatMessage *)message
|
|
{
|
|
if (self.isTemporary) {
|
|
if ([self.referenceId isEqualToString:message.referenceId]) {
|
|
return YES;
|
|
}
|
|
} else {
|
|
if (self.messageId == message.messageId) {
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
@end
|