Closed Lusaka321 closed 7 years ago
static const NSUInteger widthPadding = 40.0f;
static const NSUInteger maxCharactersNumber = 1024; // 0 - unlimited
@interface ChatViewController () < QMChatServiceDelegate, UITextViewDelegate, QMChatConnectionDelegate, QMChatAttachmentServiceDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIActionSheetDelegate, QMChatCellDelegate, UIAlertViewDelegate, QMDeferredQueueManagerDelegate
@property (nonatomic, weak) QBUUser opponentUser; @property (nonatomic, strong) MessageStatusStringBuilder stringBuilder; @property (nonatomic, strong) NSMapTable attachmentCells; @property (nonatomic, readonly) UIImagePickerController pickerController; @property (nonatomic, strong) NSTimer *typingTimer; @property (nonatomic, strong) id observerWillResignActive;
@property (nonatomic, strong) NSMutableSet *detailedCells;
@end
@implementation ChatViewController @synthesize pickerController = _pickerController;
(NSUInteger)senderID { return [QBSession currentSession].currentUser.ID; }
(NSString *)senderDisplayName { return @"Test";[QBSession currentSession].currentUser.fullName; }
(CGFloat)heightForSectionHeader { return 40.0f; }
(void)viewDidLoad { [super viewDidLoad];
[Utilities setNavigationController:self]; // self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"app_pheonix.png"]]; //[self.view setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:@"Peonix.png"]]]; self.collectionView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"Peonix.png"]]; //[[NSColor colorWithPatternImage:[NSImage imageNamed:@"Peonix"]] set]; self.collectionView.backgroundColor = [UIColor colorWithRed:240/255.0 green:240/255.0 blue:240/255.0 alpha:1.0 ]; //[[UIImage imageNamed:@"contactS"] //resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0) resizingMode:UIImageResizingModeStretch]; [self.view sendSubviewToBack:self.collectionView]; self.inputToolbar.contentView.backgroundColor = [UIColor whiteColor]; self.inputToolbar.contentView.textView.placeHolder = NSLocalizedString(@"SA_STR_MESSAGE_PLACEHOLDER", nil); self.inputToolbar.contentView.textView.layer.borderColor = [UIColor whiteColor].CGColor; self.attachmentCells = [NSMapTable strongToWeakObjectsMapTable]; self.stringBuilder = [MessageStatusStringBuilder new]; self.detailedCells = [NSMutableSet set];
self.enableTextCheckingTypes = NSTextCheckingAllTypes;
[self updateTitle]; [Utilities addMenuView:self];
if (self.dialog.type == QBChatDialogTypePrivate) {
// Handling 'typing' status.
__weak typeof(self)weakSelf = self;
[self.dialog setOnUserIsTyping:^(NSUInteger userID) {
__typeof(weakSelf)strongSelf = weakSelf;
if ([QBSession currentSession].currentUser.ID == userID) {
return;
}
strongSelf.title = NSLocalizedString(@"SA_STR_TYPING", nil);
}];
// Handling user stopped typing.
[self.dialog setOnUserStoppedTyping:^(NSUInteger userID) {
__typeof(weakSelf)strongSelf = weakSelf;
if ([QBSession currentSession].currentUser.ID == userID) {
return;
}
[strongSelf updateTitle];
}];
}
if ([[self storedMessages] count] > 0 && self.chatDataSource.messagesCount == 0) { //inserting all messages from memory storage [self.chatDataSource addMessages:[self storedMessages]]; }
[self refreshMessagesShowingProgress:NO]; if(self.dialog.type == QBChatDialogTypePrivate) self.navigationItem.rightBarButtonItem = nil; }
(void)refreshMessagesShowingProgress:(BOOL)showingProgress {
if (showingProgress) { [SVProgressHUD showWithStatus:NSLocalizedString(@"SA_STR_LOADING_MESSAGES", nil) maskType:SVProgressHUDMaskTypeNone]; }
weak typeof(self)weakSelf = self;
// Retrieving messages from Quickblox REST history and cache. [[ServicesManager instance].chatService messagesWithChatDialogID:self.dialog.ID completion:^(QBResponse response, NSArray messages) { if (response.success) {
if ([messages count] > 0) {
[weakSelf.chatDataSource addMessages:messages];
}
[SVProgressHUD dismiss];
} else {
[SVProgressHUD showErrorWithStatus:NSLocalizedString(@"SA_STR_ERROR", nil)];
NSLog(@"can not refresh messages: %@", response.error.error);
}
}]; }
(void)deferredQueueManager:(QMDeferredQueueManager )queueManager didAddMessageLocally:(QBChatMessage )addedMessage {
if ([self.dialog.ID isEqualToString:addedMessage.dialogID]) [self.chatDataSource addMessage:addedMessage];
}
(void)deferredQueueManager:(QMDeferredQueueManager )queueManager didUpdateMessageLocally:(nonnull QBChatMessage )updatedMessage {
if ([self.dialog.ID isEqualToString:updatedMessage.dialogID]) [self.chatDataSource updateMessage:updatedMessage];
}
(NSArray *)storedMessages { return [[ServicesManager instance].chatService.messagesMemoryStorage messagesWithDialogID:self.dialog.ID]; }
(void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated];
[[ServicesManager instance].chatService addDelegate:self]; [ServicesManager instance].chatService.chatAttachmentService.delegate = self; [[self queueManager] addDelegate:self];
// Saving currently opened dialog. [ServicesManager instance].currentDialogID = self.dialog.ID;
weak typeof(self)weakSelf = self; self.observerWillResignActive = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note) { [weakSelf fireStopTypingIfNecessary]; }]; }
(void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated];
[[ServicesManager instance].chatService removeDelegate:self]; [ServicesManager instance].chatService.chatAttachmentService.delegate = nil; [[self queueManager] removeDelegate:self];
[[NSNotificationCenter defaultCenter] removeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self.observerWillResignActive];
// Deletes typing blocks. [self.dialog clearTypingStatusBlocks];
// Resetting currently opened dialog. [ServicesManager instance].currentDialogID = nil; }
(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"kShowDialogInfoViewController"]) { DialogInfoTableViewController *viewController = segue.destinationViewController; viewController.dialog = self.dialog; } }
(void)updateTitle {
if (self.dialog.type == QBChatDialogTypePrivate) {
NSMutableArray *mutableOccupants = [self.dialog.occupantIDs mutableCopy];
[mutableOccupants removeObject:@([self senderID])];
NSNumber *opponentID = [mutableOccupants firstObject];
QBUUser *opponentUser = [[ServicesManager instance].usersService.usersMemoryStorage userWithID:[opponentID unsignedIntegerValue]];
if (!opponentUser) {
self.title = [opponentID stringValue];
return;
}
self.opponentUser = opponentUser;
self.title = self.opponentUser.fullName;
} else self.title = self.dialog.name;
}
(void)sendReadStatusForMessage:(QBChatMessage *)message {
if ([self messageShouldBeReaded:message]) { //NSLog(NSString _Nonnull format, ...); [[ServicesManager instance].chatService readMessage:message completion:^(NSError error) { // Nslog(@""); if (error != nil) { NSLog(@"Problems while marking message as read! Error: %@", error); return; } }]; } }
(BOOL)messageShouldBeReaded:(QBChatMessage *)message { return !message.isDateDividerMessage && message.senderID != self.senderID && ![message.readIDs containsObject:@(self.senderID)]; }
(void)fireStopTypingIfNecessary {
[self.typingTimer invalidate]; self.typingTimer = nil; [self.dialog sendUserStoppedTyping]; }
(void)didPressSendButton:(UIButton )button withTextAttachments:(NSArray)textAttachments senderId:(NSUInteger)senderId senderDisplayName:(NSString )senderDisplayName date:(NSDate )date {
NSTextAttachment * attachment = textAttachments.firstObject;
if (attachment.image) {
QBChatMessage *message = [QBChatMessage new];
message.senderID = self.senderID;
message.dialogID = self.dialog.ID;
message.dateSent = [NSDate date];
[self.chatDataSource addMessage:message];
[[ServicesManager instance].chatService sendAttachmentMessage:message
toDialog:self.dialog
withAttachmentImage:attachment.image
completion:^(NSError *error) {
[self.attachmentCells removeObjectForKey:message.ID];
if (error != nil) {
// perform local attachment deleting
[self.chatDataSource deleteMessage:message];
[SVProgressHUD showErrorWithStatus:error.localizedDescription];
}
}];
[self finishSendingMessageAnimated:YES];
}
}
(void)didPressSendButton:(UIButton )button withMessageText:(NSString )text senderId:(NSUInteger)senderId senderDisplayName:(NSString )senderDisplayName date:(NSDate )date {
if (![[self queueManager] shouldSendMessagesInDialogWithID:self.dialog.ID]) { return; }
if (self.typingTimer != nil) { [self fireStopTypingIfNecessary]; }
QBChatMessage message = [QBChatMessage message]; message.text = text; message.senderID = senderId; message.markable = YES; message.deliveredIDs = @[@(self.senderID)]; message.readIDs = @[@(self.senderID)]; message.dialogID = self.dialog.ID; message.dateSent = date; // Sending message. [[ServicesManager instance].chatService sendMessage:message toDialogID:self.dialog.ID saveToHistory:YES saveToStorage:YES completion:^(NSError error) {
if (error != nil) {
NSLog(@"Failed to send message with error: %@", error);
NSString * title = NSLocalizedString(@"SA_STR_ERROR", nil);
NSString * subtitle = error.localizedDescription;
UIImage *iconImage = [UIImage imageNamed:@"icon-error"];
UIColor *backgroundColor = TINT_COLOR;
[QMMessageNotificationManager showNotificationWithTitle:title
subtitle:subtitle
color:backgroundColor
iconImage:iconImage];
}
}];
[self finishSendingMessageAnimated:YES]; }
(Class)viewClassForItem:(QBChatMessage *)item {
if (item.isNotificatonMessage || item.isDateDividerMessage) {
return [QMChatNotificationCell class];
}
if (item.senderID != self.senderID) { if (item.isMediaMessage && item.attachmentStatus != QMMessageAttachmentStatusError) { return [QMChatAttachmentIncomingCell class]; } else { return [QMChatIncomingCell class]; } } else { if (item.isMediaMessage && item.attachmentStatus != QMMessageAttachmentStatusError) { return [QMChatAttachmentOutgoingCell class]; } else { return [QMChatOutgoingCell class]; } } }
(NSAttributedString )attributedStringForItem:(QBChatMessage )messageItem {
UIColor textColor; UIFont font; if (messageItem.isNotificatonMessage || messageItem.isDateDividerMessage) { textColor = [UIColor lightGrayColor]; font = [UIFont fontWithName:@"HelveticaNeue-Light" size:13.0f] ; } else { textColor = [messageItem senderID] == self.senderID ? [UIColor blackColor] : [UIColor blackColor]; font = [UIFont fontWithName:@"HelveticaNeue-Light" size:16.0f] ; }
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; paragraphStyle.lineHeightMultiple = 1.0; paragraphStyle.minimumLineHeight = font.lineHeight; paragraphStyle.maximumLineHeight = font.lineHeight;
NSDictionary *attributes = @{ NSForegroundColorAttributeName:textColor, NSFontAttributeName:font, NSParagraphStyleAttributeName: paragraphStyle};
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:messageItem.text ? messageItem.text : @"" attributes:attributes];
return attrStr; }
(NSAttributedString )topLabelAttributedStringForItem:(QBChatMessage )messageItem {
UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:13.0f];
if ([messageItem senderID] == self.senderID || self.dialog.type == QBChatDialogTypePrivate) { return nil; }
NSString *topLabelText = self.opponentUser.fullName ?: self.opponentUser.login;
if (self.dialog.type != QBChatDialogTypePrivate) { QBUUser *messageSender = [[ServicesManager instance].usersService.usersMemoryStorage userWithID:messageItem.senderID];
if (messageSender) {
topLabelText = messageSender.login;
}
else {
topLabelText = [NSString stringWithFormat:@"@%lu",(unsigned long)messageItem.senderID];
}
}
// setting the paragraph style lineBreakMode to NSLineBreakByTruncatingTail in order to TTTAttributedLabel cut the line in a correct way NSMutableParagraphStyle paragraphStyle = [[NSMutableParagraphStyle alloc] init]; paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail; NSDictionary attributes = @{ NSForegroundColorAttributeName:[UIColor lightGrayColor], NSFontAttributeName:font, NSParagraphStyleAttributeName: paragraphStyle};
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:topLabelText attributes:attributes];
return attrStr; }
(NSAttributedString )bottomLabelAttributedStringForItem:(QBChatMessage )messageItem {
UIColor *textColor = [messageItem senderID] == self.senderID ? [UIColor lightGrayColor] : [UIColor colorWithWhite:0.000 alpha:0.7f];
UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:11.0f];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
paragraphStyle.minimumLineHeight = font.lineHeight; paragraphStyle.maximumLineHeight = font.lineHeight;
NSDictionary *attributes = @{ NSForegroundColorAttributeName:textColor, NSFontAttributeName:font, NSParagraphStyleAttributeName: paragraphStyle};
NSString text = messageItem.dateSent ? [self timeStampWithDate:messageItem.dateSent] : @""; if ([messageItem senderID] == self.senderID) { text = [NSString stringWithFormat:@"%@\n%@", text, [self.stringBuilder statusFromMessage:messageItem]]; } NSMutableAttributedString attrStr = [[NSMutableAttributedString alloc] initWithString:text attributes:attributes];
return attrStr; }
(CGSize)collectionView:(QMChatCollectionView )collectionView dynamicSizeAtIndexPath:(NSIndexPath )indexPath maxWidth:(CGFloat)maxWidth {
QBChatMessage *item = [self.chatDataSource messageForIndexPath:indexPath]; Class viewClass = [self viewClassForItem:item]; CGSize size = CGSizeZero;
if (viewClass == [QMChatAttachmentIncomingCell class]) {
size = CGSizeMake(MIN(200, maxWidth), 200);
} else if (viewClass == [QMChatAttachmentOutgoingCell class]) {
NSAttributedString *attributedString = [self bottomLabelAttributedStringForItem:item];
CGSize bottomLabelSize = [TTTAttributedLabel sizeThatFitsAttributedString:attributedString
withConstraints:CGSizeMake(MIN(200, maxWidth), CGFLOAT_MAX)
limitedToNumberOfLines:0];
size = CGSizeMake(MIN(200, maxWidth), 200 + ceilf(bottomLabelSize.height));
} else if (viewClass == [QMChatNotificationCell class]) {
NSAttributedString *attributedString = [self attributedStringForItem:item];
size = [TTTAttributedLabel sizeThatFitsAttributedString:attributedString
withConstraints:CGSizeMake(maxWidth, CGFLOAT_MAX)
limitedToNumberOfLines:0];
} else {
NSAttributedString *attributedString = [self attributedStringForItem:item];
size = [TTTAttributedLabel sizeThatFitsAttributedString:attributedString
withConstraints:CGSizeMake(maxWidth, CGFLOAT_MAX)
limitedToNumberOfLines:0];
}
return size; }
(CGFloat)collectionView:(QMChatCollectionView )collectionView minWidthAtIndexPath:(NSIndexPath )indexPath {
QBChatMessage *item = [self.chatDataSource messageForIndexPath:indexPath];
CGSize size = CGSizeZero; if ([self.detailedCells containsObject:item.ID]) {
size = [TTTAttributedLabel sizeThatFitsAttributedString:[self bottomLabelAttributedStringForItem:item]
withConstraints:CGSizeMake(CGRectGetWidth(self.collectionView.frame) - widthPadding, CGFLOAT_MAX)
limitedToNumberOfLines:0];
}
if (self.dialog.type != QBChatDialogTypePrivate) {
CGSize topLabelSize = [TTTAttributedLabel sizeThatFitsAttributedString:[self topLabelAttributedStringForItem:item]
withConstraints:CGSizeMake(CGRectGetWidth(self.collectionView.frame) - widthPadding, CGFLOAT_MAX)
limitedToNumberOfLines:0];
if (topLabelSize.width > size.width) {
size = topLabelSize;
}
}
return size.width; }
/**
Allows to perform copy action for QMChatIncomingCell and QMChatOutgoingCell */
QBChatMessage *item = [self.chatDataSource messageForIndexPath:indexPath]; Class viewClass = [self viewClassForItem:item];
if (viewClass == [QMChatNotificationCell class] || viewClass == [QMChatContactRequestCell class]){
return NO;
}
return [super collectionView:collectionView canPerformAction:action forItemAtIndexPath:indexPath withSender:sender]; }
/**
Allows to perform copy action for QMChatIncomingCell and QMChatOutgoingCell */
if (action == @selector(copy:)) {
QBChatMessage *message = [self.chatDataSource messageForIndexPath:indexPath];
if ([message isMediaMessage]) {
[[ServicesManager instance].chatService.chatAttachmentService localImageForAttachmentMessage:message completion:^(NSError *error, UIImage *image) {
if (image) {
[[UIPasteboard generalPasteboard] setValue:UIImageJPEGRepresentation(image, 1)
forPasteboardType:(NSString *)kUTTypeJPEG];
}
}];
}
else {
[[UIPasteboard generalPasteboard] setString:message.text];
}
} }
(NSString )timeStampWithDate:(NSDate )date {
static NSDateFormatter *dateFormatter = nil;
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateFormat = @"HH:mm"; });
NSString *timeStamp = [dateFormatter stringFromDate:date];
return timeStamp; }
(QMChatCellLayoutModel)collectionView:(QMChatCollectionView )collectionView layoutModelAtIndexPath:(NSIndexPath )indexPath { QMChatCellLayoutModel layoutModel = [super collectionView:collectionView layoutModelAtIndexPath:indexPath];
layoutModel.avatarSize = (CGSize){0.0, 0.0}; layoutModel.topLabelHeight = 0.0f; layoutModel.maxWidthMarginSpace = 20.0f;
QBChatMessage *item = [self.chatDataSource messageForIndexPath:indexPath]; Class class = [self viewClassForItem:item];
if (class == [QMChatAttachmentIncomingCell class] || class == [QMChatIncomingCell class]) {
if (self.dialog.type != QBChatDialogTypePrivate) {
NSAttributedString *topLabelString = [self topLabelAttributedStringForItem:item];
CGSize size = [TTTAttributedLabel sizeThatFitsAttributedString:topLabelString
withConstraints:CGSizeMake(CGRectGetWidth(self.collectionView.frame) - widthPadding, CGFLOAT_MAX)
limitedToNumberOfLines:1];
layoutModel.topLabelHeight = size.height;
}
layoutModel.spaceBetweenTopLabelAndTextView = 5.0f;
} else if (class == [QMChatNotificationCell class]) {
layoutModel.spaceBetweenTopLabelAndTextView = 5.0f;
}
CGSize size = CGSizeZero; if ([self.detailedCells containsObject:item.ID]) { NSAttributedString *bottomAttributedString = [self bottomLabelAttributedStringForItem:item]; size = [TTTAttributedLabel sizeThatFitsAttributedString:bottomAttributedString withConstraints:CGSizeMake(CGRectGetWidth(self.collectionView.frame) - widthPadding, CGFLOAT_MAX) limitedToNumberOfLines:0]; } layoutModel.bottomLabelHeight = ceilf(size.height);
layoutModel.spaceBetweenTextViewAndBottomLabel = 5.0f;
return layoutModel; }
(void)collectionView:(QMChatCollectionView )collectionView configureCell:(UICollectionViewCell )cell forIndexPath:(NSIndexPath *)indexPath { [super collectionView:collectionView configureCell:cell forIndexPath:indexPath];
QMChatCell chatCell = (QMChatCell )cell;
// subscribing to cell delegate [chatCell setDelegate:self]; chatCell.containerView.cornerRadius = 10.0; [chatCell containerView].highlightColor = [UIColor colorWithWhite:0.5 alpha:0.5];
QBChatMessage *message = [self.chatDataSource messageForIndexPath:indexPath];
if ([cell isKindOfClass:[QMChatOutgoingCell class]]) {
QMMessageStatus status = [[self queueManager] statusForMessage:message];
switch (status) {
case QMMessageStatusSent: {
[chatCell containerView].bgColor = [UIColor whiteColor];
break;
}
case QMMessageStatusSending: {
[chatCell containerView].bgColor = [UIColor colorWithRed:0.761 green:0.772 blue:0.746 alpha:1.000];
break;
}
case QMMessageStatusNotSent: {
[chatCell containerView].bgColor = [UIColor colorWithRed:1.000 green:0.190 blue:0.108 alpha:1.000];
break;
}
}
} else if ([cell isKindOfClass:[QMChatAttachmentOutgoingCell class]]) { [chatCell containerView].bgColor = [UIColor whiteColor]; } else if ([cell isKindOfClass:[QMChatIncomingCell class]] || [cell isKindOfClass:[QMChatAttachmentIncomingCell class]]) { [chatCell containerView].bgColor = [UIColor whiteColor]; } else if ([cell isKindOfClass:[QMChatNotificationCell class]]) { [chatCell containerView].bgColor = self.collectionView.backgroundColor; // avoid tapping for Notification Cell cell.userInteractionEnabled = NO; }
if (![cell conformsToProtocol:@protocol(QMChatAttachmentCell)]) { return; }
if (message.attachments == nil) { return; }
QBChatAttachment *attachment = message.attachments.firstObject;
NSMutableArray *keysToRemove = [NSMutableArray array];
NSEnumerator enumerator = [self.attachmentCells keyEnumerator]; NSString existingAttachmentID = nil; while (existingAttachmentID = [enumerator nextObject]) { UICollectionViewCell *cachedCell = [self.attachmentCells objectForKey:existingAttachmentID]; if ([cachedCell isEqual:cell]) { [keysToRemove addObject:existingAttachmentID]; } }
for (NSString *key in keysToRemove) { [self.attachmentCells removeObjectForKey:key]; }
[self.attachmentCells setObject:cell forKey:attachment.ID];
[(id
__weak typeof(self)weakSelf = self; // Getting image from chat attachment service. [[ServicesManager instance].chatService.chatAttachmentService imageForAttachmentMessage:message completion:^(NSError error, UIImage image) { //
if ([(id<QMChatAttachmentCell>)cell attachmentID] != attachment.ID) return;
[weakSelf.attachmentCells removeObjectForKey:attachment.ID];
if (error != nil) {
[SVProgressHUD showErrorWithStatus:error.localizedDescription];
} else {
if (image != nil) {
[(id<QMChatAttachmentCell>)cell setAttachmentImage:image];
[cell updateConstraints];
}
}
}];
}
(void)collectionView:(UICollectionView )collectionView willDisplayCell:(UICollectionViewCell )__unused cell forItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.item == [self.collectionView numberOfItemsInSection:0] - 1) { // the very first message // load more if exists __weak typeof(self)weakSelf = self; // Getting earlier messages for chat dialog identifier. [[[ServicesManager instance].chatService loadEarlierMessagesWithChatDialogID:self.dialog.ID] continueWithBlock:^id(BFTask *task) {
if ([task.result count] > 0) {
[weakSelf.chatDataSource addMessages:task.result];
}
return nil;
}];
}
// marking message as read if needed QBChatMessage *itemMessage = [self.chatDataSource messageForIndexPath:indexPath]; [self sendReadStatusForMessage:itemMessage]; }
(void)chatCellDidTapContainer:(QMChatCell *)cell {
NSIndexPath indexPath = [self.collectionView indexPathForCell:cell]; QBChatMessage currentMessage = [self.chatDataSource messageForIndexPath:indexPath]; QMMessageStatus status = [[self queueManager] statusForMessage:currentMessage];
if (status == QMMessageStatusNotSent && currentMessage.senderID == self.senderID) {
[self handleNotSentMessage:currentMessage];
return;
}
if ([self.detailedCells containsObject:currentMessage.ID]) { [self.detailedCells removeObject:currentMessage.ID]; } else { [self.detailedCells addObject:currentMessage.ID]; }
[self.collectionView.collectionViewLayout removeSizeFromCacheForItemID:currentMessage.ID]; [self.collectionView performBatchUpdates:nil completion:nil]; }
(void)chatCell:(QMChatCell )__unused cell didTapOnTextCheckingResult:(NSTextCheckingResult )textCheckingResult {
switch (textCheckingResult.resultType) {
case NSTextCheckingTypeLink: {
if ([SFSafariViewController class] != nil &&
// SFSafariViewController supporting only http and https schemes
([textCheckingResult.URL.scheme.lowercaseString isEqualToString:@"http"]
|| [textCheckingResult.URL.scheme.lowercaseString isEqualToString:@"https"])) {
SFSafariViewController *controller = [[SFSafariViewController alloc] initWithURL:textCheckingResult.URL entersReaderIfAvailable:false];
[self presentViewController:controller animated:true completion:nil];
}
else {
if ([[UIApplication sharedApplication] canOpenURL:textCheckingResult.URL]) {
[[UIApplication sharedApplication] openURL:textCheckingResult.URL];
}
}
break;
}
case NSTextCheckingTypePhoneNumber: {
if (![self canMakeACall]) {
[SVProgressHUD showInfoWithStatus:NSLocalizedString(@"Your Device can't make a phone call", nil) maskType:SVProgressHUDMaskTypeNone];
break;
}
NSString *urlString = [NSString stringWithFormat:@"tel:%@", textCheckingResult.phoneNumber];
NSURL *url = [NSURL URLWithString:urlString];
[self.view endEditing:YES];
void (^callAction)(void) = ^ {
[[UIApplication sharedApplication] openURL:url];
};
if ([UIAlertController class]) {
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:nil
message:textCheckingResult.phoneNumber
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"SA_STR_CANCEL", nil)
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * _Nonnull __unused action) {
}]];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"SA_STR_CALL", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull __unused action) {
callAction();
}]];
[self presentViewController:alertController animated:YES completion:nil];
}
else {
[UIAlertView showWithTitle:@"" message:textCheckingResult.phoneNumber cancelButtonTitle:NSLocalizedString(@"SA_STR_CANCEL", nil) otherButtonTitles:@[NSLocalizedString(@"SA_STR_CALL", nil)] tapBlock:^(UIAlertView *alertView, NSInteger buttonIndex) {
if (buttonIndex == 0) {
callAction();
}
}];
}
break;
}
default:
break;
} }
(void)chatCell:(QMChatCell *)cell didPerformAction:(SEL)action withSender:(id)sender {
}
}
}
(void)chatService:(QMChatService )chatService didDeleteChatDialogWithIDFromMemoryStorage:(NSString )chatDialogID {
if ([self.dialog.ID isEqualToString:chatDialogID]) {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@""
message:@"You have left this dialog"
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"SA_STR_OK", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[self.navigationController popViewControllerAnimated:YES];
}]];
[self presentViewController:alertController
animated:YES
completion:NULL];
} }
(void)chatService:(QMChatService )chatService didLoadMessagesFromCache:(NSArray )messages forDialogID:(NSString *)dialogID {
if ([self.dialog.ID isEqualToString:dialogID]) {
[self.chatDataSource addMessages:messages];
} }
(void)chatService:(QMChatService )chatService didAddMessageToMemoryStorage:(QBChatMessage )message forDialogID:(NSString *)dialogID {
if ([self.dialog.ID isEqualToString:dialogID]) { // Inserting message received from XMPP or self sent if ([self.chatDataSource messageExists:message]) { [self.chatDataSource updateMessage:message]; } else { [self.chatDataSource addMessage:message]; } } }
(void)chatService:(QMChatService )chatService didUpdateChatDialogInMemoryStorage:(QBChatDialog )chatDialog {
if (self.dialog.type != QBChatDialogTypePrivate && [self.dialog.ID isEqualToString:chatDialog.ID]) { self.dialog = chatDialog; self.title = self.dialog.name; } }
(void)chatService:(QMChatService )chatService didUpdateMessage:(QBChatMessage )message forDialogID:(NSString *)dialogID {
if ([self.dialog.ID isEqualToString:dialogID] && message.senderID == self.senderID) {
[self.chatDataSource updateMessage:message];
} }
(void)chatService:(QMChatService )chatService didUpdateMessages:(NSArray )messages forDialogID:(NSString *)dialogID {
if ([self.dialog.ID isEqualToString:dialogID]) {
[self.chatDataSource updateMessages:messages];
} }
(void)chatServiceChatDidConnect:(QMChatService *)chatService {
[self refreshMessagesShowingProgress:YES]; }
(void)chatServiceChatDidReconnect:(QMChatService *)chatService {
[self refreshMessagesShowingProgress:YES]; }
(void)chatAttachmentService:(QMChatAttachmentService )chatAttachmentService didChangeAttachmentStatus:(QMMessageAttachmentStatus)status forMessage:(QBChatMessage )message {
if (status != QMMessageAttachmentStatusNotLoaded) {
if ([message.dialogID isEqualToString:self.dialog.ID]) {
[self.chatDataSource updateMessage:message];
}
} }
(void)chatAttachmentService:(QMChatAttachmentService )chatAttachmentService didChangeLoadingProgress:(CGFloat)progress forChatAttachment:(QBChatAttachment )attachment {
id
[cell updateLoadingProgress:progress];
} }
(void)chatAttachmentService:(QMChatAttachmentService )chatAttachmentService didChangeUploadingProgress:(CGFloat)progress forMessage:(QBChatMessage )message {
id
if (cell == nil && progress < 1.0f) {
NSIndexPath *indexPath = [self.chatDataSource indexPathForMessage:message];
cell = (UICollectionViewCell <QMChatAttachmentCell> *)[self.collectionView cellForItemAtIndexPath:indexPath];
[self.attachmentCells setObject:cell forKey:message.ID];
}
[cell updateLoadingProgress:progress]; }
(void)textViewDidChange:(UITextView *)textView {
[super textViewDidChange:textView]; }
(BOOL)textView:(UITextView )textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString )text {
if ([((QMPlaceHolderTextView)textView) hasTextAttachment]) { if (text.length == 0) { [((QMPlaceHolderTextView)textView) setDefaultSettings]; return YES; } return NO; }
// Prevent crashing undo bug if(range.length + range.location > textView.text.length) { return NO; }
if (![ServicesManager instance].isAuthorized) {
return YES;
}
if (self.typingTimer) {
[self.typingTimer invalidate];
self.typingTimer = nil;
} else {
[self.dialog sendUserIsTyping];
}
self.typingTimer = [NSTimer scheduledTimerWithTimeInterval:4.0 target:self selector:@selector(fireStopTypingIfNecessary) userInfo:nil repeats:NO];
if (maxCharactersNumber > 0) {
if (textView.text.length >= maxCharactersNumber && text.length > 0) {
[self showCharactersNumberError];
return NO;
}
NSString * newText = [textView.text stringByReplacingCharactersInRange:range withString:text];
if ([newText length] <= maxCharactersNumber || text.length == 0) {
return YES;
}
NSInteger symbolsToCut = maxCharactersNumber - textView.text.length;
NSRange stringRange = {0, MIN([text length], symbolsToCut)};
// adjust the range to include dependent chars
stringRange = [text rangeOfComposedCharacterSequencesForRange:stringRange];
// Now you can create the short string
NSString *shortString = [text substringWithRange:stringRange];
NSMutableString * newtext = textView.text.mutableCopy;
[newtext insertString:shortString atIndex:range.location];
textView.text = newtext.copy;
[self showCharactersNumberError];
[self textViewDidChange:textView];
return NO;
}
return YES; }
(void)textViewDidEndEditing:(UITextView *)textView { [super textViewDidEndEditing:textView];
[self fireStopTypingIfNecessary]; }
(BOOL)placeHolderTextView:(QMPlaceHolderTextView *)textView shouldPasteWithSender:(id)sender {
if ([UIPasteboard generalPasteboard].image) {
/* Variant 1*/
// // If there's an image in the pasteboard, construct a message with that image and `send` it.
//
// QBChatMessage *message = [QBChatMessage new];
// message.senderID = self.senderID;
// message.dialogID = self.dialog.ID;
// message.dateSent = [NSDate date];
//
// [[ServicesManager instance].chatService sendAttachmentMessage:message
// toDialog:self.dialog
// withAttachmentImage:[UIPasteboard generalPasteboard].image
// completion:^(NSError *error) {
//
// [self.attachmentCells removeObjectForKey:message.ID];
//
// if (error != nil) {
// [SVProgressHUD showErrorWithStatus:error.localizedDescription];
//
// // perform local attachment deleting
// [[ServicesManager instance].chatService deleteMessageLocally:message];
// [self.chatSectionManager deleteMessage:message];
// }
// }];
/* Variant 2*/
NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
textAttachment.image = [UIPasteboard generalPasteboard].image;
textAttachment.bounds = CGRectMake(0, 0, 100, 100);
NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment];
[self.inputToolbar.contentView.textView setAttributedText:attrStringWithImage];
[self textViewDidChange:self.inputToolbar.contentView.textView];
return NO;
} return YES; }
(void)didPickAttachmentImage:(UIImage *)image {
QBChatMessage *message = [QBChatMessage new]; message.senderID = self.senderID; message.dialogID = self.dialog.ID; message.dateSent = [NSDate date];
__weak typeof(self)weakSelf = self; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ __typeof(weakSelf)strongSelf = weakSelf; UIImage *newImage = image; if (strongSelf.pickerController.sourceType == UIImagePickerControllerSourceTypeCamera) { newImage = [newImage fixOrientation]; }
UIImage *resizedImage = [strongSelf resizedImageFromImage:newImage];
// Sending attachment to the dialog.
dispatch_async(dispatch_get_main_queue(), ^{
[self.chatDataSource addMessage:message];
[[ServicesManager instance].chatService sendAttachmentMessage:message
toDialog:strongSelf.dialog
withAttachmentImage:resizedImage
completion:^(NSError *error) {
[strongSelf.attachmentCells removeObjectForKey:message.ID];
if (error != nil) {
[strongSelf.chatDataSource deleteMessage:message];
[SVProgressHUD showErrorWithStatus:error.localizedDescription];
}
}];
});
}); }
(void)showCharactersNumberError {
NSString * title = NSLocalizedString(@"SA_STR_ERROR", nil);
NSString * subtitle = [NSString stringWithFormat:@"The character limit is %lu. ", (unsigned long)maxCharactersNumber];
[QMMessageNotificationManager showNotificationWithTitle:title
subtitle:subtitle
type:QMMessageNotificationTypeWarning];
}
(BOOL)canMakeACall {
BOOL canMakeACall = false;
if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"tel://"]]) {
// Check if iOS Device supports phone calls
CTTelephonyNetworkInfo netInfo = [[CTTelephonyNetworkInfo alloc] init];
CTCarrier carrier = [netInfo subscriberCellularProvider];
NSString *mnc = [carrier mobileNetworkCode];
// User will get an alert error when they will try to make a phone call in airplane mode.
if (([mnc length] == 0)) {
// Device cannot place a call at this time. SIM might be removed.
} else {
// iOS Device is capable for making calls
canMakeACall = true;
}
} else {
// iOS Device is not capable for making calls
}
return canMakeACall;
}
(UIImage )resizedImageFromImage:(UIImage )image {
CGFloat largestSide = image.size.width > image.size.height ? image.size.width : image.size.height; CGFloat scaleCoefficient = largestSide / 560.0f; CGSize newSize = CGSizeMake(image.size.width / scaleCoefficient, image.size.height / scaleCoefficient);
UIGraphicsBeginImageContext(newSize);
[image drawInRect:(CGRect){0, 0, newSize.width, newSize.height}]; UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resizedImage; }
(void)handleNotSentMessage:(QBChatMessage*)notSentMessage {
UIAlertController * alertVC = [UIAlertController alertControllerWithTitle:@"" message:NSLocalizedString(@"SA_STR_MESSAGE_FAILED_TO_SEND", nil) preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction resend = [UIAlertAction actionWithTitle:NSLocalizedString(@"SA_STR_TRY_AGAIN_MESSAGE", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction action)
{
[[self queueManager] perfromDefferedActionForMessage:notSentMessage withCompletion:nil];
[alertVC dismissViewControllerAnimated:YES completion:nil];
}];
UIAlertAction delete = [UIAlertAction actionWithTitle:NSLocalizedString(@"SA_STR_DELETE_MESSAGE", nil) style:UIAlertActionStyleDestructive handler:^(UIAlertAction action) { [self.chatDataSource deleteMessage:notSentMessage]; [[self queueManager] removeMessage:notSentMessage];
[alertVC dismissViewControllerAnimated:YES completion:nil];
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"SA_STR_CANCEL", nil) style:UIAlertActionStyleCancel handler:nil];
[alertVC addAction:resend]; [alertVC addAction:delete]; [alertVC addAction:cancel];
[self presentViewController:alertVC animated:YES completion:nil];
}
@end
All very intriguing but unrelated to crouton issues from what I can determine (tl;dr).
Hello dennis, Have sent full code.Just to run the code send button i need to modify.You can check the code full this link:"https://github.com/QuickBlox/quickblox-ios-sdk/blob/master/sample-chat/sample-chat/ViewController/ChatViewController.m"
@Lusaka321,
I can barely do 'crouton' let alone 'quickblox' so I won't be of any help whatsoever. Please take this to the 'quickblox' issue site.
-DennisL
issue:Send button I need to add one image. want to add time now. want to ad double tik just like whats up.
If known, describe the steps to reproduce the issue: