Commit 36d0fe08 authored by Kevin Reynaldo's avatar Kevin Reynaldo
Browse files

Merge branch 'release/2.5.0'

parents 30b8fc8c c17166c3
......@@ -3,7 +3,7 @@ Pod::Spec.new do |s|
# ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
s.name = "TapTalk"
s.version = "2.4.0"
s.version = "2.5.0"
s.summary = "TapTalk.io is a complete in-app chat SDK and messaging API. TapTalk.io provides UI-based and code-based implementation & fully customizable."
s.homepage = "https://taptalk.io"
......
......@@ -2245,7 +2245,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 2.4.0;
MARKETING_VERSION = 2.5.0;
PRODUCT_BUNDLE_IDENTIFIER = io.TapTalk.TapTalk;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
......@@ -2282,7 +2282,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 2.4.0;
MARKETING_VERSION = 2.5.0;
PRODUCT_BUNDLE_IDENTIFIER = io.TapTalk.TapTalk;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
......
......@@ -1148,7 +1148,7 @@ static void addRoundedRectToPath(CGContextRef context, CGRect rect, float ovalWi
return NO;
}
if (message.type == TAPChatMessageTypeText || message.type == TAPChatMessageTypeImage || message.type == TAPChatMessageTypeVideo) {
if (message.type == TAPChatMessageTypeText || message.type == TAPChatMessageTypeLink || message.type == TAPChatMessageTypeImage || message.type == TAPChatMessageTypeVideo) {
NSString *firstPredicateString = [NSString stringWithFormat:@" @%@ ", activeUserUsername];
NSString *secondPredicateString = [NSString stringWithFormat:@" @%@\n", activeUserUsername];
......
......@@ -69,6 +69,7 @@ typedef NS_ENUM(NSInteger, TAPAPIManagerType) {
TAPAPIManagerTypeUnPinMessage,
TAPAPIManagerTypeGetPinnedMessages,
TAPAPIManagerTypeGetPinnedMessagesIDs,
TAPAPIManagerTypeGetSharedContent,
};
@interface TAPAPIManager : NSObject
......
......@@ -288,6 +288,10 @@ static NSString * const kAPIVersionString = @"v1";
NSString *apiPath = @"chat/message/get_pinned_ids";
return [NSString stringWithFormat:@"%@/%@/%@", self.APIBaseURL, kAPIVersionString, apiPath];
}
else if (type == TAPAPIManagerTypeGetSharedContent) {
NSString *apiPath = @"chat/room/get_shared_content";
return [NSString stringWithFormat:@"%@/%@/%@", self.APIBaseURL, kAPIVersionString, apiPath];
}
return [NSString stringWithFormat:@"%@", self.APIBaseURL];
}
......
......@@ -388,6 +388,27 @@
//Divide message if length more than character limit
NSInteger characterLimit = kCharacterLimit;
//check is text message containing url
NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:NULL];
NSArray *urlMatches = [linkDetector matchesInString:textMessage options:0 range:NSMakeRange(0, textMessage.length)];
NSMutableArray *urlMatchesString = [[NSMutableArray alloc] init];
for(NSTextCheckingResult *urlMatch in urlMatches){
NSURL *url = [urlMatch URL];
[urlMatchesString addObject:[url absoluteString]];
}
TAPChatMessageType messagetype = TAPChatMessageTypeText;
NSDictionary *messageData = nil;
if(urlMatches.count > 0){
messagetype = TAPChatMessageTypeLink;
NSString *firstUrl = [urlMatchesString objectAtIndex:0];
messageData = @{@"url":firstUrl, @"urls":urlMatchesString};
}
if ([textMessage length] > characterLimit) {
NSInteger messageLength = [textMessage length];
......@@ -402,8 +423,8 @@
TAPMessageModel *message = [self createMessageModelWithRoom:room
body:substringMessage
type:TAPChatMessageTypeText
messageData:nil];
type:messagetype
messageData:messageData];
//Call block in TAPCoreMessageManager to handle things in TAPCore
successGenerateMessage(message);
......@@ -414,8 +435,8 @@
else {
TAPMessageModel *message = [self createMessageModelWithRoom:room
body:textMessage
type:TAPChatMessageTypeText
messageData:nil];
type:messagetype
messageData:messageData];
//Call block in TAPCoreMessageManager to handle things in TAPCore
successGenerateMessage(message);
......
......@@ -392,6 +392,9 @@ NS_ASSUME_NONNULL_BEGIN
- (void)getPinnedMessageIDsWithRoomID:(NSString *)roomID
success:(void (^)(NSArray<NSString *> *pinnedMessagesIDs))success
failure:(void (^)(NSError *error))failure;
- (void)getSharedContentMessagesWithRoomID:(NSString *)roomID maxCreated:(long)maxCreated minCreated:(long)minCreated
success:(void (^)(NSArray <TAPMessageModel *> *mediaMessagesArray, NSArray <TAPMessageModel *> *fileMessagesArray, NSArray <TAPMessageModel *> *linkMessagesArray))success
failure:(void (^)(NSError *error))failure;
@end
......
......@@ -1714,13 +1714,39 @@
success:(void (^)(TAPMessageModel *message))success
failure:(void (^)(TAPMessageModel * _Nullable message, NSError *error))failure {
if (message.type == TAPChatMessageTypeText) {
if (message.type == TAPChatMessageTypeText || message.type == TAPChatMessageTypeLink) {
if (updatedText.length > kCharacterLimit) {
NSString *errorMessage = [NSString stringWithFormat:@"Message exceeds the %ld character limit", (long)kCharacterLimit];
NSError *error = [[TAPCoreErrorManager sharedManager] generateLocalizedErrorWithErrorCode:90306 errorMessage:errorMessage];
failure(message, error);
return;
}
//check is text message containing url
NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:NULL];
NSArray *urlMatches = [linkDetector matchesInString:updatedText options:0 range:NSMakeRange(0, updatedText.length)];
NSMutableArray *urlMatchesString = [[NSMutableArray alloc] init];
for(NSTextCheckingResult *urlMatch in urlMatches){
NSURL *url = [urlMatch URL];
[urlMatchesString addObject:[url absoluteString]];
}
TAPChatMessageType messagetype = TAPChatMessageTypeText;
NSDictionary *messageData = nil;
if(urlMatches.count > 0){
messagetype = TAPChatMessageTypeLink;
NSString *firstUrl = [urlMatchesString objectAtIndex:0];
messageData = @{@"url":firstUrl, @"urls":urlMatchesString};
}
message.type = messagetype;
message.data = messageData;
message.body = updatedText;
}
else if (message.type == TAPChatMessageTypeImage || message.type == TAPChatMessageTypeVideo) {
......@@ -1882,4 +1908,17 @@
}];
}
- (void)getSharedContentMessagesWithRoomID:(NSString *)roomID maxCreated:(long)maxCreated minCreated:(long)minCreated
success:(void (^)(NSArray <TAPMessageModel *> *mediaMessagesArray, NSArray <TAPMessageModel *> *fileMessagesArray, NSArray <TAPMessageModel *> *linkMessagesArray))success
failure:(void (^)(NSError *error))failure {
[TAPDataManager callAPIGetSharedContent:roomID maxCreated:maxCreated minCreated:minCreated success:^(NSArray <TAPMessageModel *> *mediaMessagesArray, NSArray <TAPMessageModel *> *linkMessagesArray, NSArray <TAPMessageModel *> *fileMessagesArray) {
success(mediaMessagesArray, fileMessagesArray, linkMessagesArray);
} failure:^(NSError *error) {
failure(error);
}];
}
@end
......@@ -142,6 +142,16 @@
numberOfItem:(NSInteger)numberOfItem
success:(void (^)(NSArray *mediaMessages))success
failure:(void (^)(NSError *error))failure;
+ (void)getDatabaseFileMessagesInRoomWithRoomID:(NSString *)roomID
lastTimestamp:(NSString *)lastTimestamp
numberOfItem:(NSInteger)numberOfItem
success:(void (^)(NSArray *fileMessages))success
failure:(void (^)(NSError *error))failure;
+ (void)getDatabaseLinkMessagesInRoomWithRoomID:(NSString *)roomID
lastTimestamp:(NSString *)lastTimestamp
numberOfItem:(NSInteger)numberOfItem
success:(void (^)(NSArray *linkMessages))success
failure:(void (^)(NSError *error))failure;
+ (void)getDatabaseUnreadRoomCountWithActiveUserID:(NSString *)activeUserID
success:(void (^)(NSArray *unreadRoomIDs))success
failure:(void (^)(NSError *))failure;
......@@ -418,6 +428,10 @@
OTPID:(NSString *)OTPID
OTPKey:(NSString *)OTPKey deletionReason:(NSString *)deletionReason success:(void (^)(NSNumber *isSuccess))success
failure:(void (^)(NSError *error))failure;
+ (void)callAPIGetSharedContent:(NSString *)roomID
maxCreated:(long)maxCreated
minCreated:(long)minCreated success:(void (^)(NSArray <TAPMessageModel *> *mediaMessagesArray, NSArray <TAPMessageModel *> *fileMessagesArray, NSArray <TAPMessageModel *> *linkMessagesArray))success
failure:(void (^)(NSError *error))failure;
+ (NSDictionary *)dictionaryFromMessageModel:(TAPMessageModel *)message;
// Used to prevent inserting message to deleted chat room
......
......@@ -2390,7 +2390,7 @@
NSString *subQueryMentionValidationString = [NSString stringWithFormat:@"(body LIKE '%@' || body LIKE '%@' || body LIKE '%@' || body LIKE '%@' || body LIKE '%@' || body LIKE '%@' || body LIKE '%@' || body LIKE '%@' || body LIKE '%@')", firstPredicateString, secondPredicateString, thirdPredicateString, fourthPredicateString, fifthPredicateString, sixthPredicateString, seventhPredicateString, eighthPredicateString, ninthPredicateString];
NSString *queryString = [NSString stringWithFormat:@"isRead == 0 && roomID LIKE '%@' && !(userID LIKE '%@') && (type == %ld || type == %ld || type == %ld) && %@", roomID, activeUserID, TAPChatMessageTypeText, TAPChatMessageTypeImage, TAPChatMessageTypeVideo, subQueryMentionValidationString];
NSString *queryString = [NSString stringWithFormat:@"isRead == 0 && roomID LIKE '%@' && !(userID LIKE '%@') && (type == %ld || type == %ld || type == %ld || type == %ld) && %@", roomID, activeUserID, TAPChatMessageTypeText, TAPChatMessageTypeLink, TAPChatMessageTypeImage, TAPChatMessageTypeVideo, subQueryMentionValidationString];
[TAPDatabaseManager loadDataFromTableName:kDatabaseTableMessage whereClauseQuery:queryString sortByColumnName:@"created" isAscending:YES success:^(NSArray *resultArray) {
resultArray = [TAPUtil nullToEmptyArray:resultArray];
......@@ -2440,6 +2440,70 @@
}];
}
+ (void)getDatabaseFileMessagesInRoomWithRoomID:(NSString *)roomID
lastTimestamp:(NSString *)lastTimestamp
numberOfItem:(NSInteger)numberOfItem
success:(void (^)(NSArray *fileMessages))success
failure:(void (^)(NSError *error))failure {
NSString *queryString = [NSString stringWithFormat:@"isHidden == 0 && isDeleted == 0 && isFailedSend != 1 && isSending != 1 && roomID LIKE '%@' && created < %lf && (type == %ld || type == %ld)", roomID, [lastTimestamp doubleValue], TAPChatMessageTypeFile];
if ([lastTimestamp isEqualToString:@""]) {
queryString = [NSString stringWithFormat:@"isHidden == 0 && isDeleted == 0 && isFailedSend != 1 && isSending != 1 && roomID LIKE '%@' && (type == %ld || type == %ld)", roomID, TAPChatMessageTypeFile];
}
[TAPDatabaseManager loadDataFromTableName:kDatabaseTableMessage whereClauseQuery:queryString sortByColumnName:@"created" isAscending:NO success:^(NSArray *resultArray) {
resultArray = [TAPUtil nullToEmptyArray:resultArray];
NSMutableArray *obtainedArray = [NSMutableArray array];
for (NSDictionary *databaseDictionary in resultArray) {
TAPMessageModel *message = [TAPDataManager messageModelFromDictionary:databaseDictionary];
[obtainedArray addObject:message];
if ([obtainedArray count] == numberOfItem) {
break;
}
}
success(obtainedArray);
} failure:^(NSError *error) {
failure(error);
}];
}
+ (void)getDatabaseLinkMessagesInRoomWithRoomID:(NSString *)roomID
lastTimestamp:(NSString *)lastTimestamp
numberOfItem:(NSInteger)numberOfItem
success:(void (^)(NSArray *linkMessages))success
failure:(void (^)(NSError *error))failure {
NSString *queryString = [NSString stringWithFormat:@"isHidden == 0 && isDeleted == 0 && isFailedSend != 1 && isSending != 1 && roomID LIKE '%@' && created < %lf && (type == %ld || type == %ld)", roomID, [lastTimestamp doubleValue], TAPChatMessageTypeLink];
if ([lastTimestamp isEqualToString:@""]) {
queryString = [NSString stringWithFormat:@"isHidden == 0 && isDeleted == 0 && isFailedSend != 1 && isSending != 1 && roomID LIKE '%@' && (type == %ld || type == %ld)", roomID, TAPChatMessageTypeLink];
}
[TAPDatabaseManager loadDataFromTableName:kDatabaseTableMessage whereClauseQuery:queryString sortByColumnName:@"created" isAscending:NO success:^(NSArray *resultArray) {
resultArray = [TAPUtil nullToEmptyArray:resultArray];
NSMutableArray *obtainedArray = [NSMutableArray array];
for (NSDictionary *databaseDictionary in resultArray) {
TAPMessageModel *message = [TAPDataManager messageModelFromDictionary:databaseDictionary];
[obtainedArray addObject:message];
if ([obtainedArray count] == numberOfItem) {
break;
}
}
success(obtainedArray);
} failure:^(NSError *error) {
failure(error);
}];
}
+ (void)getDatabaseUnreadRoomCountWithActiveUserID:(NSString *)activeUserID
success:(void (^)(NSArray *unreadRoomIDs))success
failure:(void (^)(NSError *))failure {
......@@ -7892,4 +7956,126 @@
}];
}
+ (void)callAPIGetSharedContent:(NSString *)roomID
maxCreated:(long)maxCreated
minCreated:(long)minCreated success:(void (^)(NSArray <TAPMessageModel *> *mediaMessagesArray, NSArray <TAPMessageModel *> *fileMessagesArray, NSArray <TAPMessageModel *> *linkMessagesArray))success
failure:(void (^)(NSError *error))failure {
NSString *requestURL = [[TAPAPIManager sharedManager] urlForType:TAPAPIManagerTypeGetSharedContent];
NSMutableDictionary *parameterDictionary = [NSMutableDictionary dictionary];
[parameterDictionary setObject:roomID forKey:@"roomID"];
[parameterDictionary setObject:@(maxCreated) forKey:@"maxCreated"];
[parameterDictionary setObject:@(minCreated) forKey:@"minCreated"];
[parameterDictionary setObject:@"DESC" forKey:@"sortOrder"];
[[TAPNetworkManager sharedManager] post:requestURL parameters:parameterDictionary progress:^(NSProgress *uploadProgress) {
} success:^(NSURLSessionDataTask *dataTask, NSDictionary *responseObject) {
if (![self isResponseSuccess:responseObject]) {
NSDictionary *errorDictionary = [responseObject objectForKey:@"error"];
NSString *errorMessage = [errorDictionary objectForKey:@"message"];
errorMessage = [TAPUtil nullToEmptyString:errorMessage];
NSString *errorStatusCodeString = [responseObject objectForKey:@"status"];
errorStatusCodeString = [TAPUtil nullToEmptyString:errorStatusCodeString];
NSInteger errorStatusCode = [errorStatusCodeString integerValue];
if (errorStatusCode == 401) {
//Call refresh token
[[TAPDataManager sharedManager] callAPIRefreshAccessTokenSuccess:^{
[TAPDataManager callAPIGetSharedContent:roomID maxCreated:maxCreated minCreated:minCreated success:success failure:failure];
} failure:^(NSError *error) {
failure(error);
}];
return;
}
NSInteger errorCode = [[responseObject valueForKeyPath:@"error.code"] integerValue];
if (errorMessage == nil || [errorMessage isEqualToString:@""]) {
errorCode = 999;
}
NSError *error = [NSError errorWithDomain:errorMessage code:errorCode userInfo:@{@"message": errorMessage}];
failure(error);
return;
}
NSDictionary *dataDictionary = [responseObject objectForKey:@"data"];
NSArray *mediaArray = [dataDictionary objectForKey:@"media"];
NSArray *documentArray = [dataDictionary objectForKey:@"files"];
NSArray *linkArray = [dataDictionary objectForKey:@"links"];
NSMutableArray *mediaMessageArray = [NSMutableArray array];
NSMutableArray *documentMessageArray = [NSMutableArray array];
NSMutableArray *linkMessageArray = [NSMutableArray array];
for(NSDictionary *messageDict in mediaArray) {
TAPMessageModel *decryptedMessage = [TAPEncryptorManager decryptToMessageModelFromDictionary:messageDict];
TAPUserModel *user = [TAPUserModel new];
user.userID = [messageDict objectForKey:@"userID"];
user.fullname = [messageDict objectForKey:@"userFullname"];
TAPRoomModel * room = [TAPRoomModel createPersonalRoomIDWithID:roomID name:[messageDict objectForKey:@"userFullname"] imageURL:nil];
decryptedMessage.user = user;
decryptedMessage.room = room;
decryptedMessage.type = [[messageDict objectForKey:@"messageType"] longValue];
[mediaMessageArray addObject:decryptedMessage];
}
for(NSDictionary *messageDict in documentArray) {
TAPMessageModel *decryptedMessage = [TAPEncryptorManager decryptToMessageModelFromDictionary:messageDict];
TAPUserModel *user = [TAPUserModel new];
user.userID = [messageDict objectForKey:@"userID"];
user.fullname = [messageDict objectForKey:@"userFullname"];
TAPRoomModel * room = [TAPRoomModel createPersonalRoomIDWithID:roomID name:[messageDict objectForKey:@"userFullname"] imageURL:nil];
decryptedMessage.user = user;
decryptedMessage.room = room;
decryptedMessage.type = [[messageDict objectForKey:@"messageType"] longValue];
[documentMessageArray addObject:decryptedMessage];
}
for(NSDictionary *messageDict in linkArray) {
TAPMessageModel *decryptedMessage = [TAPEncryptorManager decryptToMessageModelFromDictionary:messageDict];
TAPUserModel *user = [TAPUserModel new];
user.userID = [messageDict objectForKey:@"userID"];
user.fullname = [messageDict objectForKey:@"userFullname"];
TAPRoomModel * room = [TAPRoomModel createPersonalRoomIDWithID:roomID name:[messageDict objectForKey:@"userFullname"] imageURL:nil];
decryptedMessage.user = user;
decryptedMessage.room = room;
decryptedMessage.type = [[messageDict objectForKey:@"messageType"] longValue];
[linkMessageArray addObject:decryptedMessage];
}
success(mediaMessageArray, linkMessageArray, documentMessageArray);
} failure:^(NSURLSessionDataTask *dataTask, NSError *error) {
[TAPDataManager logErrorStringFromError:error];
#ifdef DEBUG
NSString *errorDomain = error.domain;
NSString *newDomain = [NSString stringWithFormat:@"%@ ~ %@", requestURL, errorDomain];
NSError *newError = [NSError errorWithDomain:newDomain code:error.code userInfo:error.userInfo];
failure(newError);
#else
NSError *localizedError = [NSError errorWithDomain:NSLocalizedStringFromTableInBundle(@"We are experiencing problem to connect to our server, please try again later...", nil, [TAPUtil currentBundle], @"") code:999 userInfo:@{@"message": NSLocalizedStringFromTableInBundle(@"Failed to connect to our server, please try again later...", nil, [TAPUtil currentBundle], @"")}];
failure(localizedError);
#endif
}];
}
@end
......@@ -257,6 +257,7 @@ typedef NS_ENUM(NSInteger, TAPTextColor) {
TAPTextColorActionSheetDestructiveLabel,
TAPTextColorActionSheetCancelButtonLabel,
TAPTextColorTableViewSectionHeaderLabel,
TAPTextColorSharedMediaSectionHeaderLabel,
TAPTextColorContactListName,
TAPTextColorContactListNameHighlighted,
TAPTextColorContactListUsername,
......@@ -412,6 +413,10 @@ typedef NS_ENUM(NSInteger, TAPComponentFont) {
TAPComponentFontChatProfileMenuLabel,
TAPComponentFontChatProfileTitleLabelStyle,
TAPComponentFontChatProfileMenuDestructiveLabel,
TAPComponentFontChatProfileSharedMediaSectionLabel,
TAPComponentFontChatProfileSharedMediaEmptyTitleLabel,
TAPComponentFontChatProfileSharedMediaEmptyBodyLabel,
TAPComponentFontChatProfileSharedMediaTabActiveLabel,
TAPComponentFontSearchNewContactResultName,
TAPComponentFontSearchNewContactResultUsername,
TAPComponentFontAlbumNameLabel,
......
......@@ -416,6 +416,14 @@
return font;
break;
}
case TAPComponentFontChatProfileSharedMediaTabActiveLabel:
{
UIFont *font = [[TAPStyleManager sharedManager] getDefaultFontForType:TAPDefaultFontBold];
font = [font fontWithSize:TAP_TABLEVIEW_SECTION_HEADER_LABEL_FONTSIZE_STYLE];
return font;
break;
}
case TAPComponentFontContactListName:
{
......@@ -543,6 +551,30 @@
return font;
break;
}
case TAPComponentFontChatProfileSharedMediaSectionLabel:
{
UIFont *font = [[TAPStyleManager sharedManager] getDefaultFontForType:TAPDefaultFontBold];
font = [font fontWithSize:TAP_CHAT_PROFILE_MENU_SHARED_MEDIA_SECTION_LABEL_FONTSIZE_STYLE];
return font;
break;
}
case TAPComponentFontChatProfileSharedMediaEmptyTitleLabel:
{
UIFont *font = [[TAPStyleManager sharedManager] getDefaultFontForType:TAPDefaultFontBold];
font = [font fontWithSize:TAP_CHAT_PROFILE_MENU_SHARED_MEDIA_EMPTY_LABEL_FONTSIZE_STYLE];
return font;
break;
}
case TAPComponentFontChatProfileSharedMediaEmptyBodyLabel:
{
UIFont *font = [[TAPStyleManager sharedManager] getDefaultFontForType:TAPDefaultFontRegular];
font = [font fontWithSize:TAP_CHAT_PROFILE_MENU_SHARED_MEDIA_EMPTY_LABEL_FONTSIZE_STYLE];
return font;
break;
}
case TAPComponentFontSearchNewContactResultName:
{
......@@ -1518,6 +1550,11 @@
return color;
break;
}
case TAPTextColorSharedMediaSectionHeaderLabel: {
UIColor *color = [[TAPStyleManager sharedManager] getDefaultColorForType:TAPDefaultColorTextDark];
return color;
break;
}
case TAPTextColorContactListName: {
UIColor *color = [[TAPStyleManager sharedManager] getDefaultColorForType:TAPDefaultColorTextDark];
return color;
......
{
"images" : [
{
"filename" : "Frame 97.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Frame 97@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame 97@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "tt-file-white-100.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "tt-file-white-100@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "tt-file-white-100@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment