зеркало из https://github.com/nextcloud/talk-ios.git
832 строки
34 KiB
Objective-C
832 строки
34 KiB
Objective-C
//
|
|
// NCChatViewController.m
|
|
// VideoCalls
|
|
//
|
|
// Created by Ivan Sein on 23.04.18.
|
|
// Copyright © 2018 struktur AG. All rights reserved.
|
|
//
|
|
|
|
#import "NCChatViewController.h"
|
|
|
|
#import "AFImageDownloader.h"
|
|
#import "ChatMessageTableViewCell.h"
|
|
#import "GroupedChatMessageTableViewCell.h"
|
|
#import "FileMessageTableViewCell.h"
|
|
#import "SystemMessageTableViewCell.h"
|
|
#import "DateHeaderView.h"
|
|
#import "PlaceholderView.h"
|
|
#import "NCAPIController.h"
|
|
#import "NCChatMessage.h"
|
|
#import "NCMessageParameter.h"
|
|
#import "NCChatTitleView.h"
|
|
#import "NCMessageTextView.h"
|
|
#import "NCFilePreviewSessionManager.h"
|
|
#import "NCRoomsManager.h"
|
|
#import "NCRoomController.h"
|
|
#import "NCSettingsController.h"
|
|
#import "NSDate+DateTools.h"
|
|
#import "RoomInfoTableViewController.h"
|
|
#import "UIImageView+AFNetworking.h"
|
|
#import "UnreadMessagesView.h"
|
|
|
|
@interface NCChatViewController ()
|
|
|
|
@property (nonatomic, strong) NCRoom *room;
|
|
@property (nonatomic, strong) NCRoomController *roomController;
|
|
@property (nonatomic, strong) NCChatTitleView *titleView;
|
|
@property (nonatomic, strong) PlaceholderView *chatBackgroundView;
|
|
@property (nonatomic, strong) NSMutableDictionary *messages;
|
|
@property (nonatomic, strong) NSMutableArray *dateSections;
|
|
@property (nonatomic, strong) NSMutableArray *mentions;
|
|
@property (nonatomic, strong) NSMutableArray *autocompletionUsers;
|
|
@property (nonatomic, assign) BOOL stopReceivingNewMessages;
|
|
@property (nonatomic, assign) BOOL hasReceiveInitialHistory;
|
|
@property (nonatomic, assign) BOOL retrievingHistory;
|
|
@property (nonatomic, strong) UIActivityIndicatorView *loadingHistoryView;
|
|
@property (nonatomic, assign) NSInteger firstUnreadMessage;
|
|
@property (nonatomic, strong) UnreadMessagesView *unreadMessageView;
|
|
|
|
@end
|
|
|
|
@implementation NCChatViewController
|
|
|
|
- (instancetype)initForRoom:(NCRoom *)room
|
|
{
|
|
self = [super initWithTableViewStyle:UITableViewStylePlain];
|
|
if (self) {
|
|
self.room = room;
|
|
self.hidesBottomBarWhenPushed = YES;
|
|
// Fixes problem with tableView contentSize on iOS 11
|
|
self.tableView.estimatedRowHeight = 0;
|
|
self.tableView.estimatedSectionHeaderHeight = 0;
|
|
// Register a SLKTextView subclass, if you need any special appearance and/or behavior customisation.
|
|
[self registerClassForTextView:[NCMessageTextView class]];
|
|
// Set image downloader to file preview imageviews.
|
|
AFImageDownloader *imageDownloader = [[AFImageDownloader alloc]
|
|
initWithSessionManager:[NCFilePreviewSessionManager sharedInstance]
|
|
downloadPrioritization:AFImageDownloadPrioritizationFIFO
|
|
maximumActiveDownloads:4
|
|
imageCache:[[AFAutoPurgingImageCache alloc] init]];
|
|
[FilePreviewImageView setSharedImageDownloader:imageDownloader];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didJoinRoom:) name:NCRoomsManagerDidJoinRoomNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveInitialChatHistory:) name:NCRoomControllerDidReceiveInitialChatHistoryNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveChatHistory:) name:NCRoomControllerDidReceiveChatHistoryNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveChatMessages:) name:NCRoomControllerDidReceiveChatMessagesNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateRoom:) name:NCRoomsManagerDidUpdateRoomNotification object:nil];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
}
|
|
|
|
#pragma mark - View lifecycle
|
|
|
|
- (void)viewDidLoad
|
|
{
|
|
[super viewDidLoad];
|
|
|
|
[self setTitleView];
|
|
[self configureActionItems];
|
|
|
|
// Disable room info until receiving first chat history
|
|
_titleView.userInteractionEnabled = NO;
|
|
|
|
self.messages = [[NSMutableDictionary alloc] init];
|
|
self.mentions = [[NSMutableArray alloc] init];
|
|
self.dateSections = [[NSMutableArray alloc] init];
|
|
|
|
self.bounces = NO;
|
|
self.shakeToClearEnabled = YES;
|
|
self.keyboardPanningEnabled = YES;
|
|
self.shouldScrollToBottomAfterKeyboardShows = YES;
|
|
self.inverted = NO;
|
|
|
|
[self.rightButton setTitle:NSLocalizedString(@"Send", nil) forState:UIControlStateNormal];
|
|
|
|
self.textInputbar.autoHideRightButton = YES;
|
|
self.textInputbar.maxCharCount = 1000;
|
|
self.textInputbar.counterStyle = SLKCounterStyleCountdownReversed;
|
|
self.textInputbar.counterPosition = SLKCounterPositionTop;
|
|
self.textInputbar.translucent = NO;
|
|
self.textInputbar.backgroundColor = [UIColor colorWithRed:0.98 green:0.98 blue:0.98 alpha:1.0]; //f9f9f9
|
|
|
|
[self.textInputbar.editorTitle setTextColor:[UIColor darkGrayColor]];
|
|
[self.textInputbar.editorLeftButton setTintColor:[UIColor colorWithRed:0.0/255.0 green:122.0/255.0 blue:255.0/255.0 alpha:1.0]];
|
|
[self.textInputbar.editorRightButton setTintColor:[UIColor colorWithRed:0.0/255.0 green:122.0/255.0 blue:255.0/255.0 alpha:1.0]];
|
|
|
|
self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
|
|
|
|
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
|
[self.tableView registerClass:[ChatMessageTableViewCell class] forCellReuseIdentifier:ChatMessageCellIdentifier];
|
|
[self.tableView registerClass:[GroupedChatMessageTableViewCell class] forCellReuseIdentifier:GroupedChatMessageCellIdentifier];
|
|
[self.tableView registerClass:[FileMessageTableViewCell class] forCellReuseIdentifier:FileMessageCellIdentifier];
|
|
[self.tableView registerClass:[FileMessageTableViewCell class] forCellReuseIdentifier:GroupedFileMessageCellIdentifier];
|
|
[self.tableView registerClass:[SystemMessageTableViewCell class] forCellReuseIdentifier:SystemMessageCellIdentifier];
|
|
[self.autoCompletionView registerClass:[ChatMessageTableViewCell class] forCellReuseIdentifier:AutoCompletionCellIdentifier];
|
|
[self registerPrefixesForAutoCompletion:@[@"@"]];
|
|
|
|
// Chat placeholder view
|
|
_chatBackgroundView = [[PlaceholderView alloc] init];
|
|
[_chatBackgroundView.placeholderView setHidden:YES];
|
|
[_chatBackgroundView.loadingView startAnimating];
|
|
self.tableView.backgroundView = _chatBackgroundView;
|
|
|
|
// Unread messages indicator
|
|
_firstUnreadMessage = -1;
|
|
_unreadMessageView = [[UnreadMessagesView alloc] init];
|
|
_unreadMessageView.center = self.view.center;
|
|
_unreadMessageView.frame = CGRectMake(_unreadMessageView.frame.origin.x,
|
|
-40,
|
|
_unreadMessageView.frame.size.width,
|
|
_unreadMessageView.frame.size.height);
|
|
_unreadMessageView.hidden = YES;
|
|
[self.textInputbar addSubview:_unreadMessageView];
|
|
}
|
|
|
|
- (void)viewWillAppear:(BOOL)animated
|
|
{
|
|
[super viewWillAppear:animated];
|
|
|
|
if (_stopReceivingNewMessages) {
|
|
_stopReceivingNewMessages = NO;
|
|
[[NCRoomsManager sharedInstance] startReceivingChatMessagesInRoom:_room];
|
|
}
|
|
}
|
|
|
|
- (void)viewWillDisappear:(BOOL)animated
|
|
{
|
|
[super viewWillDisappear:animated];
|
|
|
|
_stopReceivingNewMessages = YES;
|
|
[[NCRoomsManager sharedInstance] stopReceivingChatMessagesInRoom:_room];
|
|
|
|
if (self.isMovingFromParentViewController) {
|
|
[[NCRoomsManager sharedInstance] leaveChatInRoom:_room];
|
|
}
|
|
}
|
|
|
|
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
|
|
{
|
|
if (_firstUnreadMessage > -1) {
|
|
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
|
|
_unreadMessageView.hidden = YES;
|
|
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
|
|
_unreadMessageView.center = self.view.center;
|
|
_unreadMessageView.frame = CGRectMake(_unreadMessageView.frame.origin.x,
|
|
-40,
|
|
_unreadMessageView.frame.size.width,
|
|
_unreadMessageView.frame.size.height);
|
|
_unreadMessageView.hidden = NO;
|
|
}];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Configuration
|
|
|
|
- (void)setTitleView
|
|
{
|
|
_titleView = [[NCChatTitleView alloc] init];
|
|
_titleView.frame = CGRectMake(0, 0, 800, 30);
|
|
_titleView.autoresizingMask=UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
|
[_titleView.title setTitle:_room.displayName forState:UIControlStateNormal];
|
|
[_titleView.title addTarget:self action:@selector(titleButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
// Set room image
|
|
switch (_room.type) {
|
|
case kNCRoomTypeOneToOneCall:
|
|
{
|
|
// Request user avatar to the server and set it if exist
|
|
[_titleView.image setImageWithURLRequest:[[NCAPIController sharedInstance] createAvatarRequestForUser:_room.name andSize:96]
|
|
placeholderImage:nil success:nil failure:nil];
|
|
}
|
|
break;
|
|
case kNCRoomTypeGroupCall:
|
|
[_titleView.image setImage:[UIImage imageNamed:@"group-bg"]];
|
|
break;
|
|
case kNCRoomTypePublicCall:
|
|
[_titleView.image setImage:(_room.hasPassword) ? [UIImage imageNamed:@"public-password-bg"] : [UIImage imageNamed:@"public-bg"]];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
self.navigationItem.titleView = _titleView;
|
|
}
|
|
|
|
- (void)configureActionItems
|
|
{
|
|
UIBarButtonItem *videoCallButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"videocall-action"]
|
|
style:UIBarButtonItemStylePlain
|
|
target:self
|
|
action:@selector(videoCallButtonPressed:)];
|
|
|
|
UIBarButtonItem *voiceCallButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"call-action"]
|
|
style:UIBarButtonItemStylePlain
|
|
target:self
|
|
action:@selector(voiceCallButtonPressed:)];
|
|
|
|
self.navigationItem.rightBarButtonItems = @[videoCallButton, voiceCallButton];
|
|
}
|
|
|
|
#pragma mark - Utils
|
|
|
|
- (NSString *)getTimeFromDate:(NSDate *)date
|
|
{
|
|
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
|
[formatter setDateFormat:@"HH:mm"];
|
|
return [formatter stringFromDate:date];
|
|
}
|
|
|
|
- (NSString *)getHeaderStringFromDate:(NSDate *)date
|
|
{
|
|
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
|
formatter.dateStyle = NSDateFormatterMediumStyle;
|
|
formatter.doesRelativeDateFormatting = YES;
|
|
return [formatter stringFromDate:date];
|
|
}
|
|
|
|
- (NSString *)createSendingMessage:(NSString *)text
|
|
{
|
|
NSString *sendingMessage = [text copy];
|
|
for (NCMessageParameter *mention in _mentions) {
|
|
sendingMessage = [sendingMessage stringByReplacingOccurrencesOfString:mention.name withString:mention.parameterId];
|
|
}
|
|
_mentions = [[NSMutableArray alloc] init];
|
|
return sendingMessage;
|
|
}
|
|
|
|
#pragma mark - Action Methods
|
|
|
|
- (void)titleButtonPressed:(id)sender
|
|
{
|
|
RoomInfoTableViewController *roomInfoVC = [[RoomInfoTableViewController alloc] initForRoom:_room];
|
|
[self.navigationController pushViewController:roomInfoVC animated:YES];
|
|
}
|
|
|
|
- (void)videoCallButtonPressed:(id)sender
|
|
{
|
|
[[NCRoomsManager sharedInstance] startCall:YES inRoom:_room];
|
|
}
|
|
|
|
- (void)voiceCallButtonPressed:(id)sender
|
|
{
|
|
[[NCRoomsManager sharedInstance] startCall:NO inRoom:_room];
|
|
}
|
|
|
|
- (void)didPressRightButton:(id)sender
|
|
{
|
|
NSString *sendingText = [self createSendingMessage:self.textView.text];
|
|
[[NCRoomsManager sharedInstance] sendChatMessage:sendingText toRoom:_room];
|
|
[super didPressRightButton:sender];
|
|
}
|
|
|
|
#pragma mark - UIScrollViewDelegate Methods
|
|
|
|
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
|
{
|
|
[super scrollViewDidScroll:scrollView];
|
|
|
|
if ([scrollView isEqual:self.tableView] && scrollView.contentOffset.y < 0) {
|
|
if ([self shouldRetireveHistory]) {
|
|
_retrievingHistory = YES;
|
|
[self showLoadingHistoryView];
|
|
NSDate *dateSection = [_dateSections objectAtIndex:0];
|
|
NCChatMessage *firstMessage = [[_messages objectForKey:dateSection] objectAtIndex:0];
|
|
[_roomController getChatHistoryFromMessagesId:firstMessage.messageId];
|
|
}
|
|
}
|
|
|
|
if (_firstUnreadMessage > -1) {
|
|
[self checkUnreadMessagesVisibility];
|
|
}
|
|
}
|
|
|
|
#pragma mark - UITextViewDelegate Methods
|
|
|
|
- (BOOL)textView:(SLKTextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
|
|
{
|
|
if ([text isEqualToString:@""]) {
|
|
UITextRange *selectedRange = [textView selectedTextRange];
|
|
NSInteger cursorOffset = [textView offsetFromPosition:textView.beginningOfDocument toPosition:selectedRange.start];
|
|
NSString *text = textView.text;
|
|
NSString *substring = [text substringToIndex:cursorOffset];
|
|
NSMutableString *lastPossibleMention = [[[substring componentsSeparatedByString:@"@"] lastObject] mutableCopy];
|
|
[lastPossibleMention insertString:@"@" atIndex:0];
|
|
for (NCMessageParameter *mention in _mentions) {
|
|
if ([lastPossibleMention isEqualToString:mention.name]) {
|
|
// Delete mention
|
|
textView.text = [[self.textView text] stringByReplacingOccurrencesOfString:lastPossibleMention withString:@""];
|
|
[_mentions removeObject:mention];
|
|
return NO;
|
|
}
|
|
}
|
|
}
|
|
|
|
return [super textView:textView shouldChangeTextInRange:range replacementText:text];
|
|
}
|
|
|
|
#pragma mark - Room Manager notifications
|
|
|
|
- (void)didUpdateRoom:(NSNotification *)notification
|
|
{
|
|
NCRoom *room = [notification.userInfo objectForKey:@"room"];
|
|
if (!room || ![room.token isEqualToString:_room.token]) {
|
|
return;
|
|
}
|
|
|
|
_room = room;
|
|
[self setTitleView];
|
|
}
|
|
|
|
- (void)didJoinRoom:(NSNotification *)notification
|
|
{
|
|
NCRoomController *roomController = [notification.userInfo objectForKey:@"roomController"];
|
|
if (![roomController.roomToken isEqualToString:_room.token]) {
|
|
return;
|
|
}
|
|
|
|
if (!_roomController) {
|
|
_roomController = roomController;
|
|
[_roomController getInitialChatHistory];
|
|
}
|
|
}
|
|
|
|
- (void)didReceiveInitialChatHistory:(NSNotification *)notification
|
|
{
|
|
NSString *room = [notification.userInfo objectForKey:@"room"];
|
|
if (![room isEqualToString:_room.token]) {
|
|
return;
|
|
}
|
|
|
|
_hasReceiveInitialHistory = YES;
|
|
_titleView.userInteractionEnabled = YES;
|
|
[_chatBackgroundView.loadingView stopAnimating];
|
|
[_chatBackgroundView.loadingView setHidden:YES];
|
|
|
|
NSMutableArray *messages = [notification.userInfo objectForKey:@"messages"];
|
|
if (messages) {
|
|
[self sortMessages:messages inDictionary:_messages];
|
|
[self.tableView reloadData];
|
|
NSMutableArray *messagesForLastDate = [_messages objectForKey:[_dateSections lastObject]];
|
|
NSIndexPath *lastMessageIndexPath = [NSIndexPath indexPathForRow:messagesForLastDate.count - 1 inSection:_dateSections.count - 1];
|
|
[self.tableView scrollToRowAtIndexPath:lastMessageIndexPath atScrollPosition:UITableViewScrollPositionNone animated:NO];
|
|
} else {
|
|
[_chatBackgroundView.placeholderView setHidden:NO];
|
|
}
|
|
}
|
|
|
|
- (void)didReceiveChatHistory:(NSNotification *)notification
|
|
{
|
|
NSString *room = [notification.userInfo objectForKey:@"room"];
|
|
if (![room isEqualToString:_room.token]) {
|
|
return;
|
|
}
|
|
|
|
NSMutableArray *messages = [notification.userInfo objectForKey:@"messages"];
|
|
if (messages) {
|
|
NSIndexPath *lastHistoryMessageIP = [self sortHistoryMessages:messages];
|
|
[self.tableView reloadData];
|
|
[self.tableView scrollToRowAtIndexPath:lastHistoryMessageIP atScrollPosition:UITableViewScrollPositionNone animated:NO];
|
|
}
|
|
|
|
_retrievingHistory = NO;
|
|
[self hideLoadingHistoryView];
|
|
}
|
|
|
|
- (void)didReceiveChatMessages:(NSNotification *)notification
|
|
{
|
|
NSString *room = [notification.userInfo objectForKey:@"room"];
|
|
if (![room isEqualToString:_room.token]) {
|
|
return;
|
|
}
|
|
|
|
NSMutableArray *messages = [notification.userInfo objectForKey:@"messages"];
|
|
if (messages.count > 0) {
|
|
NSInteger lastSectionBeforeUpdate = _dateSections.count - 1;
|
|
BOOL singleMessage = (messages.count == 1);
|
|
BOOL scroll = [self shouldScrollOnNewMessages];
|
|
if (!scroll && _firstUnreadMessage < 0) {
|
|
[self showNewMessagesViewUntilMessage:[messages firstObject]];
|
|
}
|
|
|
|
[self sortMessages:messages inDictionary:_messages];
|
|
|
|
NSMutableArray *messagesForLastDate = [_messages objectForKey:[_dateSections lastObject]];
|
|
NSIndexPath *lastMessageIndexPath = [NSIndexPath indexPathForRow:messagesForLastDate.count - 1 inSection:_dateSections.count - 1];
|
|
|
|
if (messages.count > 1) {
|
|
[self.tableView reloadData];
|
|
} else if (singleMessage) {
|
|
[self.tableView beginUpdates];
|
|
NSInteger newLastSection = _dateSections.count - 1;
|
|
BOOL newSection = lastSectionBeforeUpdate != newLastSection;
|
|
if (newSection) {
|
|
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:newLastSection] withRowAnimation:UITableViewRowAnimationNone];
|
|
} else {
|
|
[self.tableView insertRowsAtIndexPaths:@[lastMessageIndexPath] withRowAnimation:UITableViewRowAnimationNone];
|
|
}
|
|
[self.tableView endUpdates];
|
|
}
|
|
|
|
if (scroll) {
|
|
[self.tableView scrollToRowAtIndexPath:lastMessageIndexPath atScrollPosition:UITableViewScrollPositionNone animated:YES];
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#pragma mark - Chat functions
|
|
|
|
- (NSDate *)getKeyForDate:(NSDate *)date inDictionary:(NSDictionary *)dictionary
|
|
{
|
|
NSDate *keyDate = nil;
|
|
for (NSDate *key in dictionary.allKeys) {
|
|
if ([[NSCalendar currentCalendar] isDate:date inSameDayAsDate:key]) {
|
|
keyDate = key;
|
|
}
|
|
}
|
|
return keyDate;
|
|
}
|
|
|
|
- (NSIndexPath *)sortHistoryMessages:(NSMutableArray *)historyMessages
|
|
{
|
|
NSMutableDictionary *historyDict = [[NSMutableDictionary alloc] init];
|
|
[self sortMessages:historyMessages inDictionary:historyDict];
|
|
|
|
NSDate *chatSection = nil;
|
|
NSMutableArray *historyMessagesForSection = nil;
|
|
// Sort history sections
|
|
NSMutableArray *historySections = [NSMutableArray arrayWithArray:historyDict.allKeys];
|
|
[historySections sortUsingSelector:@selector(compare:)];
|
|
|
|
for (NSDate *historySection in historySections) {
|
|
historyMessagesForSection = [historyDict objectForKey:historySection];
|
|
chatSection = [self getKeyForDate:historySection inDictionary:_messages];
|
|
if (!chatSection) {
|
|
[_messages setObject:historyMessagesForSection forKey:historySection];
|
|
}
|
|
}
|
|
|
|
[self sortDateSections];
|
|
|
|
NSMutableArray *lastHistoryMessages = [historyDict objectForKey:[historySections lastObject]];
|
|
NSIndexPath *lastHistoryMessageIP = [NSIndexPath indexPathForRow:lastHistoryMessages.count - 1 inSection:historySections.count - 1];
|
|
|
|
if (chatSection) {
|
|
NSMutableArray *chatMessages = [_messages objectForKey:chatSection];
|
|
NCChatMessage *lastHistoryMessage = [historyMessagesForSection lastObject];
|
|
NCChatMessage *firstChatMessage = [chatMessages firstObject];
|
|
|
|
BOOL canGroup = [self shouldGroupMessage:firstChatMessage withMessage:lastHistoryMessage];
|
|
if (canGroup) {
|
|
firstChatMessage.groupMessage = YES;
|
|
firstChatMessage.groupMessageNumber = lastHistoryMessage.groupMessageNumber + 1;
|
|
for (int i = 1; i < chatMessages.count; i++) {
|
|
NCChatMessage *currentMessage = chatMessages[i];
|
|
NCChatMessage *messageBefore = chatMessages[i-1];
|
|
if ([self shouldGroupMessage:currentMessage withMessage:messageBefore]) {
|
|
currentMessage.groupMessage = YES;
|
|
currentMessage.groupMessageNumber = messageBefore.groupMessageNumber + 1;
|
|
} else if ([currentMessage.actorId isEqualToString:messageBefore.actorId] &&
|
|
(currentMessage.timestamp - messageBefore.timestamp) < kChatMessageGroupTimeDifference &&
|
|
messageBefore.groupMessageNumber == kChatMessageMaxGroupNumber) {
|
|
// Check if message groups need to be changed
|
|
currentMessage.groupMessage = NO;
|
|
currentMessage.groupMessageNumber = 0;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
[historyMessagesForSection addObjectsFromArray:chatMessages];
|
|
[_messages setObject:historyMessagesForSection forKey:chatSection];
|
|
}
|
|
|
|
return lastHistoryMessageIP;
|
|
}
|
|
|
|
- (void)sortMessages:(NSMutableArray *)messages inDictionary:(NSMutableDictionary *)dictionary
|
|
{
|
|
for (NCChatMessage *newMessage in messages) {
|
|
NSDate *newMessageDate = [NSDate dateWithTimeIntervalSince1970: newMessage.timestamp];
|
|
NSDate *keyDate = [self getKeyForDate:newMessageDate inDictionary:dictionary];
|
|
NSMutableArray *messagesForDate = [dictionary objectForKey:keyDate];
|
|
if (messagesForDate) {
|
|
NCChatMessage *lastMessage = [messagesForDate lastObject];
|
|
if ([self shouldGroupMessage:newMessage withMessage:lastMessage]) {
|
|
newMessage.groupMessage = YES;
|
|
newMessage.groupMessageNumber = lastMessage.groupMessageNumber + 1;
|
|
}
|
|
[messagesForDate addObject:newMessage];
|
|
} else {
|
|
NSMutableArray *newMessagesInDate = [NSMutableArray new];
|
|
[dictionary setObject:newMessagesInDate forKey:newMessageDate];
|
|
[newMessagesInDate addObject:newMessage];
|
|
}
|
|
}
|
|
|
|
[self sortDateSections];
|
|
}
|
|
|
|
- (void)sortDateSections
|
|
{
|
|
_dateSections = [NSMutableArray arrayWithArray:_messages.allKeys];
|
|
[_dateSections sortUsingSelector:@selector(compare:)];
|
|
}
|
|
|
|
- (BOOL)shouldGroupMessage:(NCChatMessage *)newMessage withMessage:(NCChatMessage *)lastMessage
|
|
{
|
|
BOOL sameActor = [newMessage.actorId isEqualToString:lastMessage.actorId];
|
|
BOOL sameType = ([newMessage isSystemMessage] == [lastMessage isSystemMessage]);
|
|
BOOL timeDiff = (newMessage.timestamp - lastMessage.timestamp) < kChatMessageGroupTimeDifference;
|
|
BOOL notMaxGroup = lastMessage.groupMessageNumber < kChatMessageMaxGroupNumber;
|
|
|
|
return sameActor & sameType & timeDiff & notMaxGroup;
|
|
}
|
|
|
|
- (BOOL)shouldRetireveHistory
|
|
{
|
|
return _hasReceiveInitialHistory && !_retrievingHistory && [_roomController hasHistory];
|
|
}
|
|
|
|
- (void)showLoadingHistoryView
|
|
{
|
|
_loadingHistoryView = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
|
|
_loadingHistoryView.color = [UIColor darkGrayColor];
|
|
[_loadingHistoryView startAnimating];
|
|
self.tableView.tableHeaderView = _loadingHistoryView;
|
|
}
|
|
|
|
- (void)hideLoadingHistoryView
|
|
{
|
|
_loadingHistoryView = nil;
|
|
self.tableView.tableHeaderView = nil;
|
|
}
|
|
|
|
- (BOOL)shouldScrollOnNewMessages
|
|
{
|
|
// Scroll if table view is at the bottom (or 80px up)
|
|
CGFloat minimumOffset = (self.tableView.contentSize.height - self.tableView.frame.size.height) - 80;
|
|
if (self.tableView.contentOffset.y >= minimumOffset) {
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (void)showNewMessagesViewUntilMessage:(NCChatMessage *)message
|
|
{
|
|
_firstUnreadMessage = message.messageId;
|
|
_unreadMessageView.hidden = NO;
|
|
}
|
|
|
|
- (void)hideNewMessagesView
|
|
{
|
|
_firstUnreadMessage = -1;
|
|
_unreadMessageView.hidden = YES;
|
|
}
|
|
|
|
- (void)checkUnreadMessagesVisibility
|
|
{
|
|
NSArray* cells = self.tableView.visibleCells;
|
|
for (ChatTableViewCell *cell in cells) {
|
|
if (cell.messageId == _firstUnreadMessage) {
|
|
[self hideNewMessagesView];
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark - Autocompletion
|
|
|
|
- (void)didChangeAutoCompletionPrefix:(NSString *)prefix andWord:(NSString *)word
|
|
{
|
|
if ([prefix isEqualToString:@"@"]) {
|
|
[self showSuggestionsForString:word];
|
|
}
|
|
}
|
|
|
|
- (CGFloat)heightForAutoCompletionView
|
|
{
|
|
return kChatMessageCellMinimumHeight * self.autocompletionUsers.count;
|
|
}
|
|
|
|
- (void)showSuggestionsForString:(NSString *)string
|
|
{
|
|
self.autocompletionUsers = nil;
|
|
[[NCAPIController sharedInstance] getMentionSuggestionsInRoom:_room.token forString:string withCompletionBlock:^(NSMutableArray *mentions, NSError *error) {
|
|
if (!error) {
|
|
self.autocompletionUsers = [[NSMutableArray alloc] initWithArray:mentions];
|
|
BOOL show = (self.autocompletionUsers.count > 0);
|
|
// Check if the '@' is still there
|
|
[self.textView lookForPrefixes:self.registeredPrefixes completion:^(NSString *prefix, NSString *word, NSRange wordRange) {
|
|
if (prefix.length > 0 && word.length > 0) {
|
|
[self showAutoCompletionView:show];
|
|
} else {
|
|
[self cancelAutoCompletion];
|
|
}
|
|
}];
|
|
}
|
|
}];
|
|
}
|
|
|
|
#pragma mark - UITableViewDataSource Methods
|
|
|
|
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
|
{
|
|
if ([tableView isEqual:self.autoCompletionView]) {
|
|
return 1;
|
|
}
|
|
|
|
return _dateSections.count;
|
|
}
|
|
|
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
|
{
|
|
if ([tableView isEqual:self.autoCompletionView]) {
|
|
return _autocompletionUsers.count;
|
|
}
|
|
|
|
NSDate *date = [_dateSections objectAtIndex:section];
|
|
NSMutableArray *messages = [_messages objectForKey:date];
|
|
|
|
if ([tableView isEqual:self.tableView] && messages.count > 0) {
|
|
self.tableView.backgroundView = nil;
|
|
}
|
|
|
|
return messages.count;
|
|
}
|
|
|
|
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
|
{
|
|
if ([tableView isEqual:self.autoCompletionView]) {
|
|
return nil;
|
|
}
|
|
|
|
NSDate *date = [_dateSections objectAtIndex:section];
|
|
return [self getHeaderStringFromDate:date];
|
|
}
|
|
|
|
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
|
|
{
|
|
if ([tableView isEqual:self.autoCompletionView]) {
|
|
return 0;
|
|
}
|
|
|
|
return kDateHeaderViewHeight;
|
|
}
|
|
|
|
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
|
|
{
|
|
if ([tableView isEqual:self.autoCompletionView]) {
|
|
return nil;
|
|
}
|
|
|
|
DateHeaderView *headerView = [[DateHeaderView alloc] init];
|
|
headerView.dateLabel.text = [self tableView:tableView titleForHeaderInSection:section];
|
|
headerView.dateLabel.layer.cornerRadius = 12;
|
|
headerView.dateLabel.clipsToBounds = YES;
|
|
|
|
return headerView;
|
|
}
|
|
|
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
if ([tableView isEqual:self.autoCompletionView]) {
|
|
NSDictionary *suggestion = [_autocompletionUsers objectAtIndex:indexPath.row];
|
|
NSString *suggestionId = [suggestion objectForKey:@"id"];
|
|
NSString *suggestionName = [suggestion objectForKey:@"label"];
|
|
ChatMessageTableViewCell *suggestionCell = (ChatMessageTableViewCell *)[self.autoCompletionView dequeueReusableCellWithIdentifier:AutoCompletionCellIdentifier];
|
|
suggestionCell.titleLabel.text = suggestionName;
|
|
// Request user avatar to the server and set it if exist
|
|
[suggestionCell.avatarView setImageWithURLRequest:[[NCAPIController sharedInstance] createAvatarRequestForUser:suggestionId andSize:96]
|
|
placeholderImage:nil success:nil failure:nil];
|
|
return suggestionCell;
|
|
}
|
|
|
|
NSDate *sectionDate = [_dateSections objectAtIndex:indexPath.section];
|
|
NCChatMessage *message = [[_messages objectForKey:sectionDate] objectAtIndex:indexPath.row];
|
|
UITableViewCell *cell = [UITableViewCell new];
|
|
if (message.isSystemMessage) {
|
|
SystemMessageTableViewCell *systemCell = (SystemMessageTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:SystemMessageCellIdentifier];
|
|
systemCell.bodyTextView.attributedText = message.systemMessageFormat;
|
|
systemCell.messageId = message.messageId;
|
|
if (!message.groupMessage) {
|
|
NSDate *date = [[NSDate alloc] initWithTimeIntervalSince1970:message.timestamp];
|
|
systemCell.dateLabel.text = [self getTimeFromDate:date];
|
|
}
|
|
return systemCell;
|
|
}
|
|
if (message.filePreview) {
|
|
NSString *fileCellIdentifier = (message.groupMessage) ? GroupedFileMessageCellIdentifier : FileMessageCellIdentifier;
|
|
FileMessageTableViewCell *fileCell = (FileMessageTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:fileCellIdentifier];
|
|
fileCell.titleLabel.text = message.actorDisplayName;
|
|
fileCell.bodyTextView.attributedText = message.parsedMessage;
|
|
fileCell.messageId = message.messageId;
|
|
NSDate *date = [[NSDate alloc] initWithTimeIntervalSince1970:message.timestamp];
|
|
fileCell.dateLabel.text = [self getTimeFromDate:date];
|
|
[fileCell.avatarView setImageWithURLRequest:[[NCAPIController sharedInstance] createAvatarRequestForUser:message.actorId andSize:96]
|
|
placeholderImage:nil success:nil failure:nil];
|
|
[fileCell.previewImageView setImageWithURLRequest:[[NCFilePreviewSessionManager sharedInstance] createPreviewRequestForFile:message.filePreview width:120 height:120]
|
|
placeholderImage:[UIImage imageNamed:@"file-default-preview"] success:nil failure:nil];
|
|
return fileCell;
|
|
}
|
|
if (message.groupMessage) {
|
|
GroupedChatMessageTableViewCell *groupedCell = (GroupedChatMessageTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:GroupedChatMessageCellIdentifier];
|
|
groupedCell.bodyTextView.attributedText = message.parsedMessage;
|
|
groupedCell.messageId = message.messageId;
|
|
return groupedCell;
|
|
} else {
|
|
ChatMessageTableViewCell *normalCell = (ChatMessageTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:ChatMessageCellIdentifier];
|
|
normalCell.titleLabel.text = message.actorDisplayName;
|
|
normalCell.bodyTextView.attributedText = message.parsedMessage;
|
|
normalCell.messageId = message.messageId;
|
|
NSDate *date = [[NSDate alloc] initWithTimeIntervalSince1970:message.timestamp];
|
|
normalCell.dateLabel.text = [self getTimeFromDate:date];
|
|
|
|
if ([message.actorType isEqualToString:@"guests"]) {
|
|
normalCell.titleLabel.text = ([message.actorDisplayName isEqualToString:@""]) ? @"Guest" : message.actorDisplayName;
|
|
[normalCell setGuestAvatar:message.actorDisplayName];
|
|
} else {
|
|
[normalCell.avatarView setImageWithURLRequest:[[NCAPIController sharedInstance] createAvatarRequestForUser:message.actorId andSize:96]
|
|
placeholderImage:nil success:nil failure:nil];
|
|
}
|
|
|
|
return normalCell;
|
|
}
|
|
|
|
return cell;
|
|
}
|
|
|
|
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
if ([tableView isEqual:self.tableView]) {
|
|
NSDate *sectionDate = [_dateSections objectAtIndex:indexPath.section];
|
|
NCChatMessage *message = [[_messages objectForKey:sectionDate] objectAtIndex:indexPath.row];
|
|
|
|
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
|
|
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
|
|
paragraphStyle.alignment = NSTextAlignmentLeft;
|
|
|
|
CGFloat pointSize = [ChatMessageTableViewCell defaultFontSize];
|
|
|
|
NSDictionary *attributes = @{NSFontAttributeName: [UIFont systemFontOfSize:pointSize],
|
|
NSParagraphStyleAttributeName: paragraphStyle};
|
|
|
|
CGFloat width = CGRectGetWidth(tableView.frame) - kChatMessageCellAvatarHeight;
|
|
width -= (message.isSystemMessage)? 80.0 : 30.0; // 4*right(10) + dateLabel(40) : 3*right(10)
|
|
|
|
CGRect titleBounds = [message.actorDisplayName boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:NULL];
|
|
CGRect bodyBounds = [message.parsedMessage boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:NULL];
|
|
|
|
if (message.message.length == 0) {
|
|
return 0.0;
|
|
}
|
|
|
|
CGFloat height = CGRectGetHeight(titleBounds);
|
|
height += CGRectGetHeight(bodyBounds);
|
|
height += 40.0;
|
|
|
|
if (height < kChatMessageCellMinimumHeight) {
|
|
height = kChatMessageCellMinimumHeight;
|
|
}
|
|
|
|
if (message.groupMessage || message.isSystemMessage) {
|
|
height = CGRectGetHeight(bodyBounds) + 20;
|
|
|
|
if (height < kGroupedChatMessageCellMinimumHeight) {
|
|
height = kGroupedChatMessageCellMinimumHeight;
|
|
}
|
|
}
|
|
|
|
if (message.filePreview) {
|
|
height += kFileMessageCellFilePreviewHeight + 10;
|
|
}
|
|
|
|
return height;
|
|
}
|
|
else {
|
|
return kChatMessageCellMinimumHeight;
|
|
}
|
|
}
|
|
|
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
if ([tableView isEqual:self.autoCompletionView]) {
|
|
NCMessageParameter *mention = [[NCMessageParameter alloc] init];
|
|
mention.parameterId = [NSString stringWithFormat:@"@%@", [self.autocompletionUsers[indexPath.row] objectForKey:@"id"]];
|
|
mention.name = [NSString stringWithFormat:@"@%@", [self.autocompletionUsers[indexPath.row] objectForKey:@"label"]];
|
|
[_mentions addObject:mention];
|
|
|
|
NSMutableString *mentionString = [[self.autocompletionUsers[indexPath.row] objectForKey:@"label"] mutableCopy];
|
|
[mentionString appendString:@" "];
|
|
[self acceptAutoCompletionWithString:mentionString keepPrefix:YES];
|
|
}
|
|
}
|
|
|
|
@end
|