Created
February 13, 2016 21:52
-
-
Save kevb10/f8daff4555088d64d9d4 to your computer and use it in GitHub Desktop.
Chat View using quickblox
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ChatViewController.m | |
// sample-chat | |
// | |
// Created by Andrey Moskvin on 6/9/15. | |
// Copyright (c) 2015 Igor Khomenko. All rights reserved. | |
// | |
#import "ChatViewController.h" | |
#import "DialogInfoTableViewController.h" | |
#import <UIColor+QM.h> | |
#import <UIImage+QM.h> | |
#import <TTTAttributedLabel/TTTAttributedLabel.h> | |
#import "ServicesManager.h" | |
#import "LoginTableViewController.h" | |
#import "DialogsViewController.h" | |
#import "MessageStatusStringBuilder.h" | |
#import "UIImage+fixOrientation.h" | |
#import <QMCollectionViewFlowLayoutInvalidationContext.h> | |
#import <TWMessageBarManager.h> | |
static const NSUInteger widthPadding = 40.0f; | |
@interface ChatViewController () | |
< | |
QMChatServiceDelegate, | |
QMChatConnectionDelegate, | |
UITextViewDelegate, | |
QMChatAttachmentServiceDelegate, | |
UIImagePickerControllerDelegate, | |
UINavigationControllerDelegate, | |
UIActionSheetDelegate, | |
QMChatCellDelegate | |
> | |
@property (nonatomic, weak) QBUUser* opponentUser; | |
@property (nonatomic, strong) id<NSObject> observerDidBecomeActive; | |
@property (nonatomic, strong) MessageStatusStringBuilder* stringBuilder; | |
@property (nonatomic, strong) NSMapTable* attachmentCells; | |
@property (nonatomic, readonly) UIImagePickerController* pickerController; | |
@property (nonatomic, strong) NSTimer* typingTimer; | |
@property (nonatomic, strong) id observerDidEnterBackground; | |
@property (nonatomic, strong) NSArray* unreadMessages; | |
@property (nonatomic, strong) NSMutableSet *detailedCells; | |
@end | |
@implementation ChatViewController | |
@synthesize pickerController = _pickerController; | |
- (UIImagePickerController *)pickerController | |
{ | |
if (_pickerController == nil) { | |
_pickerController = [UIImagePickerController new]; | |
_pickerController.delegate = self; | |
} | |
return _pickerController; | |
} | |
#pragma mark - Override | |
- (NSUInteger)senderID | |
{ | |
return [QBSession currentSession].currentUser.ID; | |
} | |
- (NSString *)senderDisplayName | |
{ | |
return [QBSession currentSession].currentUser.fullName; | |
} | |
- (NSTimeInterval)timeIntervalBetweenSections { | |
return 300.0f; | |
} | |
- (CGFloat)heightForSectionHeader { | |
return 40.0f; | |
} | |
#pragma mark - View lifecycle | |
- (void)viewDidLoad | |
{ | |
[super viewDidLoad]; | |
self.collectionView.backgroundColor = [UIColor whiteColor]; | |
self.inputToolbar.contentView.backgroundColor = [UIColor whiteColor]; | |
self.inputToolbar.contentView.textView.placeHolder = NSLocalizedString(@"SA_STR_MESSAGE_PLACEHOLDER", nil); | |
self.attachmentCells = [NSMapTable strongToWeakObjectsMapTable]; | |
self.stringBuilder = [MessageStatusStringBuilder new]; | |
self.detailedCells = [NSMutableSet set]; | |
[self updateTitle]; | |
// Handling 'typing' status. | |
__weak typeof(self)weakSelf = self; | |
[self.dialog setOnUserIsTyping:^(NSUInteger userID) { | |
__typeof(self) strongSelf = weakSelf; | |
if ([QBSession currentSession].currentUser.ID == userID) { | |
return; | |
} | |
strongSelf.title = NSLocalizedString(@"SA_STR_MESSAGE_PLACEHOLDER", nil); | |
}]; | |
// Handling user stopped typing. | |
[self.dialog setOnUserStoppedTyping:^(NSUInteger userID) { | |
__typeof(self) strongSelf = weakSelf; | |
[strongSelf updateTitle]; | |
}]; | |
} | |
- (void)refreshMessagesShowingProgress:(BOOL)showingProgress { | |
if (showingProgress) { | |
[SVProgressHUD showWithStatus:NSLocalizedString(@"SA_STR_LOADING_MESSAGES", nil) maskType:SVProgressHUDMaskTypeClear]; | |
} | |
__weak __typeof(self)weakSelf = self; | |
// Retrieving message from Quickblox REST history and cache. | |
[[ServicesManager instance].chatService messagesWithChatDialogID:self.dialog.ID completion:^(QBResponse *response, NSArray *messages) { | |
if (response.success) { | |
__typeof(weakSelf)strongSelf = weakSelf; | |
if ([messages count] > 0) [strongSelf insertMessagesToTheBottomAnimated:messages]; | |
[SVProgressHUD dismiss]; | |
} else { | |
[SVProgressHUD showErrorWithStatus:NSLocalizedString(@"SA_STR_ERROR", nil)]; | |
NSLog(@"can not refresh messages: %@", response.error.error); | |
} | |
}]; | |
} | |
- (NSArray *)storedMessages { | |
return [[ServicesManager instance].chatService.messagesMemoryStorage messagesWithDialogID:self.dialog.ID]; | |
} | |
- (void)viewWillAppear:(BOOL)animated | |
{ | |
[super viewWillAppear:animated]; | |
__weak __typeof(self) weakSelf = self; | |
self.observerDidBecomeActive = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { | |
__typeof(self) strongSelf = weakSelf; | |
if ([[QBChat instance] isConnected]) { | |
[strongSelf refreshMessagesShowingProgress:NO]; | |
} | |
}]; | |
self.observerDidEnterBackground = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { | |
__typeof(self) strongSelf = weakSelf; | |
[strongSelf fireStopTypingIfNecessary]; | |
}]; | |
// Saving currently opened dialog. | |
[ServicesManager instance].currentDialogID = self.dialog.ID; | |
// Retrieving messages | |
if ([[self storedMessages] count] > 0 && self.totalMessagesCount == 0) { | |
[self updateDataSourceWithMessages:[self storedMessages]]; | |
[self refreshMessagesShowingProgress:NO]; | |
} else { | |
if (self.totalMessagesCount == 0) [SVProgressHUD showWithStatus:NSLocalizedString(@"SA_STR_LOADING_MESSAGES", nil) maskType:SVProgressHUDMaskTypeClear]; | |
__weak __typeof(self)weakSelf = self; | |
[[ServicesManager instance] cachedMessagesWithDialogID:self.dialog.ID block:^(NSArray *collection) { | |
// | |
__typeof(weakSelf)strongSelf = weakSelf; | |
if ([collection count] > 0) { | |
[strongSelf insertMessagesToTheBottomAnimated:collection]; | |
} | |
[strongSelf refreshMessagesShowingProgress:NO]; | |
}]; | |
} | |
} | |
- (void)viewDidAppear:(BOOL)animated | |
{ | |
[super viewDidAppear:animated]; | |
if ([self storedMessages].count > 0 && self.totalMessagesCount != [self storedMessages].count) { | |
[self insertMessagesToTheBottomAnimated:[self storedMessages]]; | |
} | |
[[ServicesManager instance].chatService addDelegate:self]; | |
[ServicesManager instance].chatService.chatAttachmentService.delegate = self; | |
if (self.shouldUpdateNavigationStack) { | |
NSMutableArray *newNavigationStack = [NSMutableArray array]; | |
[self.navigationController.viewControllers enumerateObjectsUsingBlock:^(UIViewController* obj, NSUInteger idx, BOOL *stop) { | |
if ([obj isKindOfClass:[LoginTableViewController class]] || [obj isKindOfClass:[DialogsViewController class]]) { | |
[newNavigationStack addObject:obj]; | |
} | |
}]; | |
[newNavigationStack addObject:self]; | |
[self.navigationController setViewControllers:[newNavigationStack copy] animated:NO]; | |
self.shouldUpdateNavigationStack = NO; | |
} | |
} | |
- (void)viewWillDisappear:(BOOL)animated | |
{ | |
[super viewWillDisappear:animated]; | |
[[ServicesManager instance].chatService removeDelegate:self]; | |
[[NSNotificationCenter defaultCenter] removeObserver:self.observerDidBecomeActive]; | |
[[NSNotificationCenter defaultCenter] removeObserver:self.observerDidEnterBackground]; | |
// 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]; | |
[QBRequest userWithID:[opponentID unsignedIntegerValue] successBlock:^(QBResponse * _Nonnull response, QBUUser * _Nullable user) { | |
NSAssert(user, @"opponent must exists"); | |
self.opponentUser = user; | |
self.title = self.opponentUser.fullName; | |
} errorBlock:^(QBResponse * _Nonnull response) { | |
}]; | |
} else { | |
self.title = self.dialog.name; | |
} | |
} | |
#pragma mark - Utilities | |
- (void)sendReadStatusForMessage:(QBChatMessage *)message | |
{ | |
if (message.senderID != self.senderID && ![message.readIDs containsObject:@(self.senderID)]) { | |
[[ServicesManager instance].chatService readMessage:message completion:^(NSError *error) { | |
// | |
if (error != nil) { | |
NSLog(@"Problems while marking message as read! Error: %@", error); | |
} | |
else { | |
if ([UIApplication sharedApplication].applicationIconBadgeNumber > 0) { | |
[UIApplication sharedApplication].applicationIconBadgeNumber--; | |
} | |
} | |
}]; | |
} | |
} | |
- (void)readMessages:(NSArray *)messages | |
{ | |
if ([QBChat instance].isConnected) { | |
[[ServicesManager instance].chatService readMessages:messages forDialogID:self.dialog.ID completion:^(NSError *error) { | |
// | |
}]; | |
} else { | |
self.unreadMessages = messages; | |
} | |
} | |
- (void)fireStopTypingIfNecessary | |
{ | |
[self.typingTimer invalidate]; | |
self.typingTimer = nil; | |
[self.dialog sendUserStoppedTyping]; | |
} | |
#pragma mark Tool bar Actions | |
- (void)didPressSendButton:(UIButton *)button | |
withMessageText:(NSString *)text | |
senderId:(NSUInteger)senderId | |
senderDisplayName:(NSString *)senderDisplayName | |
date:(NSDate *)date | |
{ | |
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); | |
[[TWMessageBarManager sharedInstance] showMessageWithTitle:NSLocalizedString(@"SA_STR_ERROR", nil) description:error.localizedRecoverySuggestion type:TWMessageBarMessageTypeError]; | |
} | |
}]; | |
[self finishSendingMessageAnimated:YES]; | |
} | |
#pragma mark - Cell classes | |
- (Class)viewClassForItem:(QBChatMessage *)item | |
{ | |
// TODO: check and add QMMessageTypeAcceptContactRequest, QMMessageTypeRejectContactRequest, QMMessageTypeContactRequest | |
if (item.senderID != self.senderID) { | |
if ((item.attachments != nil && item.attachments.count > 0) || item.attachmentStatus != QMMessageAttachmentStatusNotLoaded) { | |
return [QMChatAttachmentIncomingCell class]; | |
} else { | |
return [QMChatIncomingCell class]; | |
} | |
} else { | |
if ((item.attachments != nil && item.attachments.count > 0) || item.attachmentStatus != QMMessageAttachmentStatusNotLoaded) { | |
return [QMChatAttachmentOutgoingCell class]; | |
} else { | |
return [QMChatOutgoingCell class]; | |
} | |
} | |
} | |
#pragma mark - Strings builder | |
- (NSAttributedString *)attributedStringForItem:(QBChatMessage *)messageItem { | |
UIColor *textColor = [messageItem senderID] == self.senderID ? [UIColor whiteColor] : [UIColor blackColor]; | |
UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:17.0f] ; | |
NSDictionary *attributes = @{ NSForegroundColorAttributeName:textColor, NSFontAttributeName:font}; | |
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:messageItem.text ? messageItem.encodedText : @"" attributes:attributes]; | |
return attrStr; | |
} | |
- (NSAttributedString *)topLabelAttributedStringForItem:(QBChatMessage *)messageItem { | |
UIFont *font = [UIFont fontWithName:@"HelveticaNeue-Medium" size:17.0f]; | |
if ([messageItem senderID] == self.senderID || self.dialog.type == QBChatDialogTypePrivate) { | |
return nil; | |
} | |
NSString *topLabelText = self.opponentUser.fullName != nil ? self.opponentUser.fullName : self.opponentUser.login; | |
if (self.dialog.type != QBChatDialogTypePrivate) { | |
QBUUser* user = [[ServicesManager instance].usersService.usersMemoryStorage userWithID:messageItem.senderID]; | |
topLabelText = (user != nil) ? user.login : [NSString stringWithFormat:@"%lu",(unsigned long)messageItem.senderID]; | |
} | |
NSDictionary *attributes = @{ NSForegroundColorAttributeName:[UIColor colorWithRed:0 green:122.0f / 255.0f blue:1.0f alpha:1.000], NSFontAttributeName:font}; | |
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:topLabelText attributes:attributes]; | |
return attrStr; | |
} | |
- (NSAttributedString *)bottomLabelAttributedStringForItem:(QBChatMessage *)messageItem { | |
UIColor *textColor = [messageItem senderID] == self.senderID ? [UIColor colorWithWhite:1 alpha:0.7f] : [UIColor colorWithWhite:0.000 alpha:0.7f]; | |
UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:13.0f]; | |
NSDictionary *attributes = @{ NSForegroundColorAttributeName:textColor, NSFontAttributeName:font}; | |
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; | |
} | |
#pragma mark - Collection View Datasource | |
- (CGSize)collectionView:(QMChatCollectionView *)collectionView dynamicSizeAtIndexPath:(NSIndexPath *)indexPath maxWidth:(CGFloat)maxWidth { | |
QBChatMessage *item = [self 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 { | |
NSAttributedString *messagetextAttributedString = [self attributedStringForItem:item]; | |
NSAttributedString *topLabelAttributedString = [self attributedStringForItem:item]; | |
NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading; | |
CGSize maxSize = CGSizeMake(maxWidth, CGFLOAT_MAX); | |
CGRect messageTextLabelRect = [messagetextAttributedString boundingRectWithSize:maxSize options:options context:nil]; | |
CGRect topLabelTextLabelRect = [topLabelAttributedString boundingRectWithSize:maxSize options:options context:nil]; | |
NSAttributedString *desiredString = messagetextAttributedString; | |
if (messageTextLabelRect.size.width < topLabelTextLabelRect.size.width) { | |
desiredString = topLabelAttributedString; | |
} | |
size = [TTTAttributedLabel sizeThatFitsAttributedString:desiredString | |
withConstraints:CGSizeMake(maxWidth, CGFLOAT_MAX) | |
limitedToNumberOfLines:0]; | |
} | |
return size; | |
} | |
- (CGFloat)collectionView:(QMChatCollectionView *)collectionView minWidthAtIndexPath:(NSIndexPath *)indexPath { | |
QBChatMessage *item = [self 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; | |
} | |
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender | |
{ | |
Class viewClass = [self viewClassForItem:[self messageForIndexPath:indexPath]]; | |
if (viewClass == [QMChatAttachmentIncomingCell class] || viewClass == [QMChatAttachmentOutgoingCell class]) return NO; | |
return [super collectionView:collectionView canPerformAction:action forItemAtIndexPath:indexPath withSender:sender]; | |
} | |
- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender | |
{ | |
QBChatMessage* message = [self messageForIndexPath:indexPath]; | |
Class viewClass = [self viewClassForItem:message]; | |
if (viewClass == [QMChatAttachmentIncomingCell class] || viewClass == [QMChatAttachmentOutgoingCell class]) return; | |
[UIPasteboard generalPasteboard].string = message.text; | |
} | |
#pragma mark - Utility | |
- (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; | |
} | |
#pragma mark = QMChatCollectionViewDelegateFlowLayout | |
- (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; | |
QBChatMessage *item = [self messageForIndexPath:indexPath]; | |
Class class = [self viewClassForItem:item]; | |
if (class == [QMChatOutgoingCell class] || | |
class == [QMChatAttachmentOutgoingCell class]) { | |
// Outgoing cells layout model | |
} else 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; | |
} | |
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]; | |
// subscribing to cell delegate | |
[(QMChatCell *)cell setDelegate:self]; | |
[(QMChatCell *)cell containerView].highlightColor = [UIColor colorWithWhite:0.5 alpha:0.5]; | |
if ([cell isKindOfClass:[QMChatOutgoingCell class]] || [cell isKindOfClass:[QMChatAttachmentOutgoingCell class]]) { | |
[(QMChatIncomingCell *)cell containerView].bgColor = [UIColor colorWithRed:0 green:121.0f/255.0f blue:1 alpha:1.0f]; | |
} else if ([cell isKindOfClass:[QMChatIncomingCell class]] || [cell isKindOfClass:[QMChatAttachmentIncomingCell class]]) { | |
[(QMChatOutgoingCell *)cell containerView].bgColor = [UIColor colorWithRed:231.0f / 255.0f green:231.0f / 255.0f blue:231.0f / 255.0f alpha:1.0f]; | |
} | |
if ([cell conformsToProtocol:@protocol(QMChatAttachmentCell)]) { | |
QBChatMessage* message = [self messageForIndexPath:indexPath]; | |
if (message.attachments != nil) { | |
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]; | |
[(UICollectionViewCell<QMChatAttachmentCell> *)cell setAttachmentID:attachment.ID]; | |
__weak typeof(self)weakSelf = self; | |
// Getting image from chat attachment service. | |
[[ServicesManager instance].chatService.chatAttachmentService getImageForAttachmentMessage:message completion:^(NSError *error, UIImage *image) { | |
// | |
__typeof(self) strongSelf = weakSelf; | |
if ([(UICollectionViewCell<QMChatAttachmentCell> *)cell attachmentID] != attachment.ID) return; | |
[strongSelf.attachmentCells removeObjectForKey:attachment.ID]; | |
if (error != nil) { | |
[SVProgressHUD showErrorWithStatus:error.localizedDescription]; | |
} else { | |
if (image != nil) { | |
[(UICollectionViewCell<QMChatAttachmentCell> *)cell setAttachmentImage:image]; | |
[cell updateConstraints]; | |
} | |
} | |
}]; | |
} | |
} | |
} | |
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { | |
NSUInteger lastSection = [self.collectionView numberOfSections] - 1; | |
if (indexPath.section == lastSection && indexPath.item == [self.collectionView numberOfItemsInSection:lastSection] - 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 insertMessagesToTheTopAnimated:task.result]; | |
} | |
return nil; | |
}]; | |
} | |
// marking message as read if needed | |
QBChatMessage *itemMessage = [self messageForIndexPath:indexPath]; | |
[self sendReadStatusForMessage:itemMessage]; | |
return [super collectionView:collectionView cellForItemAtIndexPath:indexPath]; | |
} | |
#pragma mark - QMChatCellDelegate | |
- (void)chatCellDidTapContainer:(QMChatCell *)cell { | |
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell]; | |
QBChatMessage *currentMessage = [self messageForIndexPath:indexPath]; | |
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 *)cell didPerformAction:(SEL)action withSender:(id)sender { | |
} | |
- (void)chatCellDidTapAvatar:(QMChatCell *)cell { | |
} | |
- (void)chatCell:(QMChatCell *)cell didTapAtPosition:(CGPoint)position { | |
} | |
#pragma mark - QMChatServiceDelegate | |
- (void)chatService:(QMChatService *)chatService didAddMessageToMemoryStorage:(QBChatMessage *)message forDialogID:(NSString *)dialogID { | |
if ([self.dialog.ID isEqualToString:dialogID]) { | |
// Inserting message received from XMPP or self sent | |
[self insertMessageToTheBottomAnimated:message]; | |
} | |
} | |
- (void)chatService:(QMChatService *)chatService didUpdateChatDialogInMemoryStorage:(QBChatDialog *)chatDialog{ | |
if( [self.dialog.ID isEqualToString:chatDialog.ID] ) { | |
self.dialog = chatDialog; | |
} | |
} | |
- (void)chatService:(QMChatService *)chatService didUpdateMessage:(QBChatMessage *)message forDialogID:(NSString *)dialogID | |
{ | |
if ([self.dialog.ID isEqualToString:dialogID] && message.senderID == self.senderID) { | |
[self updateMessage:message]; | |
} | |
} | |
#pragma mark - QMChatConnectionDelegate | |
- (void)refreshAndReadMessages; | |
{ | |
[self refreshMessagesShowingProgress:YES]; | |
[self readMessages:self.unreadMessages]; | |
self.unreadMessages = nil; | |
} | |
- (void)chatServiceChatDidConnect:(QMChatService *)chatService | |
{ | |
[self refreshAndReadMessages]; | |
} | |
- (void)chatServiceChatDidReconnect:(QMChatService *)chatService | |
{ | |
[self refreshAndReadMessages]; | |
} | |
#pragma mark - QMChatAttachmentServiceDelegate | |
- (void)chatAttachmentService:(QMChatAttachmentService *)chatAttachmentService didChangeAttachmentStatus:(QMMessageAttachmentStatus)status forMessage:(QBChatMessage *)message | |
{ | |
if ([message.dialogID isEqualToString:self.dialog.ID]) { | |
[self updateMessage:message]; | |
} | |
} | |
- (void)chatAttachmentService:(QMChatAttachmentService *)chatAttachmentService didChangeLoadingProgress:(CGFloat)progress forChatAttachment:(QBChatAttachment *)attachment | |
{ | |
UICollectionViewCell<QMChatAttachmentCell>* cell = [self.attachmentCells objectForKey:attachment.ID]; | |
if (cell != nil) { | |
[cell updateLoadingProgress:progress]; | |
} | |
} | |
- (void)chatAttachmentService:(QMChatAttachmentService *)chatAttachmentService didChangeUploadingProgress:(CGFloat)progress forMessage:(QBChatMessage *)message | |
{ | |
UICollectionViewCell<QMChatAttachmentCell>* cell = [self.attachmentCells objectForKey:message.ID]; | |
if (cell == nil && progress < 1.0f) { | |
NSIndexPath *indexPath = [self indexPathForMessage:message]; | |
cell = (UICollectionViewCell <QMChatAttachmentCell> *)[self.collectionView cellForItemAtIndexPath:indexPath]; | |
[self.attachmentCells setObject:cell forKey:message.ID]; | |
} | |
if (cell != nil) { | |
[cell updateLoadingProgress:progress]; | |
} | |
} | |
#pragma mark - UITextViewDelegate | |
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text | |
{ | |
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]; | |
return YES; | |
} | |
- (void)textViewDidEndEditing:(UITextView *)textView | |
{ | |
[super textViewDidEndEditing:textView]; | |
[self fireStopTypingIfNecessary]; | |
} | |
#pragma mark - UIImagePickerControllerDelegate | |
- (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 dialog. | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
[[ServicesManager instance].chatService sendAttachmentMessage:message | |
toDialog:strongSelf.dialog | |
withAttachmentImage:resizedImage | |
completion:^(NSError *error) { | |
// | |
[strongSelf.attachmentCells removeObjectForKey:message.ID]; | |
if (error != nil) { | |
[SVProgressHUD showErrorWithStatus:error.localizedDescription]; | |
// perform local attachment deleting | |
[[ServicesManager instance].chatService deleteMessageLocally:message]; | |
[strongSelf deleteMessage:message]; | |
} | |
}]; | |
}); | |
}); | |
} | |
- (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; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment