зеркало из https://github.com/nextcloud/talk-ios.git
329 строки
13 KiB
Objective-C
329 строки
13 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 "NCAppBranding.h"
|
|
#import "NCSettingsController.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";
|
|
|
|
@interface NCChatMessage ()
|
|
{
|
|
NCMessageFileParameter *_fileParameter;
|
|
}
|
|
|
|
@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"];
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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
|
|
{
|
|
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;
|
|
|
|
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.isTemporary = _isTemporary;
|
|
messageCopy.sendingFailed = _sendingFailed;
|
|
messageCopy.isGroupMessage = _isGroupMessage;
|
|
messageCopy.isDeleting = _isDeleting;
|
|
|
|
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)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 canServerDeleteMessages = [[NCSettingsController sharedInstance] serverHasTalkCapability:kCapabilityDeleteMessages forAccountId:account.accountId];
|
|
if ([self.messageType isEqualToString:kMessageTypeComment] && !self.isDeleting && !self.file && self.timestamp >= sixHoursAgoTimestamp && canServerDeleteMessages &&
|
|
(participantType == kNCParticipantTypeOwner || participantType == kNCParticipantTypeModerator || [self isMessageFromUser:account.userId])) {
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (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;
|
|
}
|
|
|
|
- (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.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 arrayWithCapacity:matches.count];
|
|
for (int i = 0; i < matches.count; i++) {
|
|
NSTextCheckingResult *match = [matches objectAtIndex:i];
|
|
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 (i > 0) {
|
|
NCMessageParameter *lastParameter = [parameters objectAtIndex:i-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 insertObject:messageParameter atIndex:i];
|
|
}
|
|
}
|
|
|
|
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 *)systemMessageFormat
|
|
{
|
|
NSMutableAttributedString *message = [self parsedMessage];
|
|
|
|
if (@available(iOS 13.0, *)) {
|
|
//TODO: Further adjust for dark-mode ?
|
|
[message addAttribute:NSForegroundColorAttributeName value:[UIColor tertiaryLabelColor] range:NSMakeRange(0,message.length)];
|
|
} else {
|
|
[message addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithWhite:0 alpha:0.3] range:NSMakeRange(0,message.length)];
|
|
}
|
|
|
|
|
|
return message;
|
|
}
|
|
|
|
- (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;
|
|
}
|
|
|
|
@end
|