UI: conversation view

- multiline text entry
- save message from text entry per conversation
- update message frame immediately(to prevent wrong messages centering)
- add emoji panel
- prevent QR code to be shown through conversation

Change-Id: I0c7fcf7b96760038864dac4393d74e5dce2d773a
Reviewed-by: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
diff --git a/src/MessagesVC.mm b/src/MessagesVC.mm
index 6c7dc6c..90360b5 100644
--- a/src/MessagesVC.mm
+++ b/src/MessagesVC.mm
@@ -19,7 +19,6 @@
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
  */
 
-#import <QItemSelectionModel>
 #import <QPixmap>
 #import <QtMacExtras/qmacfunctions.h>
 
@@ -31,7 +30,6 @@
 #import "views/IMTableCellView.h"
 #import "views/MessageBubbleView.h"
 #import "views/NSImage+Extensions.h"
-#import "INDSequentialTextSelectionManager.h"
 #import "delegates/ImageManipulationDelegate.h"
 #import "utils.h"
 #import "views/NSColor+RingTheme.h"
@@ -44,6 +42,10 @@
 
     __unsafe_unretained IBOutlet NSTableView* conversationView;
     __unsafe_unretained IBOutlet NSView* containerView;
+    __unsafe_unretained IBOutlet NSTextField* messageField;
+    __unsafe_unretained IBOutlet IconButton *sendFileButton;
+    __unsafe_unretained IBOutlet NSLayoutConstraint* sendPanelHeight;
+    __unsafe_unretained IBOutlet NSLayoutConstraint* messagesBottomMargin;
 
     std::string convUid_;
     lrc::api::ConversationModel* convModel_;
@@ -57,9 +59,9 @@
     QMetaObject::Connection filterChangedSignal_;
     QMetaObject::Connection interactionStatusUpdatedSignal_;
     NSString* previewImage;
+    NSMutableDictionary *pendingMessagesToSend;
 }
 
-@property (nonatomic, strong, readonly) INDSequentialTextSelectionManager* selectionManager;
 
 @end
 
@@ -73,6 +75,9 @@
 CGFloat   const MESSAGE_TEXT_PADDING      = 10;
 CGFloat   const MAX_TRANSFERED_IMAGE_SIZE = 250;
 CGFloat   const BUBBLE_HEIGHT_FOR_TRANSFERED_FILE = 87;
+NSInteger const MEESAGE_MARGIN = 21;
+NSInteger const SEND_PANEL_DEFAULT_HEIGHT = 60;
+NSInteger const SEND_PANEL_MAX_HEIGHT = 120;
 
 @implementation MessagesVC
 
@@ -95,8 +100,28 @@
     [conversationView registerNib:cellNib forIdentifier:@"LeftFinishedFileView"];
     [conversationView registerNib:cellNib forIdentifier:@"RightOngoingFileView"];
     [conversationView registerNib:cellNib forIdentifier:@"RightFinishedFileView"];
+    [[conversationView.enclosingScrollView contentView] setCopiesOnScroll:NO];
+    [messageField setFocusRingType:NSFocusRingTypeNone];
+    [conversationView setWantsLayer:YES];
 }
+
+- (instancetype)initWithCoder:(NSCoder *)coder
+{
+    self = [super initWithCoder:coder];
+    if (self) {
+        pendingMessagesToSend = [[NSMutableDictionary alloc] init];
+    }
+    return self;
+}
+
+- (void)setMessage:(NSString *)newValue {
+    _message = [newValue removeEmptyLinesAtBorders];
+}
+
 -(void) clearData {
+    if (!convUid_.empty()) {
+        pendingMessagesToSend[@(convUid_.c_str())] = messageField.stringValue;
+    }
     cachedConv_ = nil;
     convUid_ = "";
     convModel_ = nil;
@@ -107,6 +132,17 @@
     QObject::disconnect(newInteractionSignal_);
 }
 
+-(void) scrollToBottom {
+    CGRect visibleRect = [conversationView enclosingScrollView].contentView.visibleRect;
+    NSRange range = [conversationView rowsInRect:visibleRect];
+    NSIndexSet* visibleIndexes = [NSIndexSet indexSetWithIndexesInRange:range];
+    NSUInteger lastvisibleRow = [visibleIndexes lastIndex];
+    if (([conversationView numberOfRows] > 0) &&
+        lastvisibleRow == ([conversationView numberOfRows] -1)) {
+        [conversationView scrollToEndOfDocument:nil];
+    }
+}
+
 -(const lrc::api::conversation::Info*) getCurrentConversation
 {
     if (convModel_ == nil || convUid_.empty())
@@ -224,8 +260,32 @@
                                           [self](){
                                               cachedConv_ = nil;
                                           });
+    if (pendingMessagesToSend[@(convUid_.c_str())]) {
+        self.message = pendingMessagesToSend[@(convUid_.c_str())];
+        [self updateSendMessageHeight];
+    } else {
+        self.message = @"";
+        if(messagesBottomMargin.constant != SEND_PANEL_DEFAULT_HEIGHT) {
+            sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
+            messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
+            [self scrollToBottom];
+        }
+    }
+    conversationView.alphaValue = 0.0;
     [conversationView reloadData];
     [conversationView scrollToEndOfDocument:nil];
+    CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"];
+    fadeIn.fromValue = [NSNumber numberWithFloat:0.0];
+    fadeIn.toValue = [NSNumber numberWithFloat:1.0];
+    fadeIn.duration = 0.4f;
+
+    [conversationView.layer addAnimation:fadeIn forKey:fadeIn.keyPath];
+    conversationView.alphaValue = 1;
+    auto* conv = [self getCurrentConversation];
+
+    if (conv == nil)
+        return;
+    [sendFileButton setEnabled:(convModel_->owner.contactModel->getContact(conv->participants[0]).profileInfo.type != lrc::api::profile::Type::SIP)];
 }
 
 #pragma mark - configure cells
@@ -469,7 +529,7 @@
 
     [result updateMessageConstraint:messageSize.width  andHeight:messageSize.height timeIsVisible:shouldDisplayTime isTopPadding: shouldApplyPadding];
     [[result.msgView textStorage] appendAttributedString:msgAttString];
-    [result.msgView checkTextInDocument:nil];
+   // [result.msgView checkTextInDocument:nil];
 
     NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
     NSArray *matches = [linkDetector matchesInString:result.msgView.string options:0 range:NSMakeRange(0, result.msgView.string.length)];
@@ -832,4 +892,83 @@
     return [NSURL fileURLWithPath:previewImage];
 }
 
+- (void) updateSendMessageHeight {
+    NSAttributedString *msgAttString = messageField.attributedStringValue;
+    NSRect frame = NSMakeRect(0, 0, messageField.frame.size.width, msgAttString.size.height);
+    NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
+    [[tv textStorage] setAttributedString:msgAttString];
+    [tv sizeToFit];
+    CGFloat height = tv.frame.size.height + MEESAGE_MARGIN * 2;
+    CGFloat newHeight = MIN(SEND_PANEL_MAX_HEIGHT, MAX(SEND_PANEL_DEFAULT_HEIGHT, height));
+    if(messagesBottomMargin.constant == newHeight) {
+        return;
+    }
+    messagesBottomMargin.constant = newHeight;
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+        [self scrollToBottom];
+        sendPanelHeight.constant = newHeight;
+    });
+}
+
+- (IBAction)sendMessage:(id)sender {
+    NSString* text = self.message;
+    if (text && text.length > 0) {
+        auto* conv = [self getCurrentConversation];
+        convModel_->sendMessage(convUid_, std::string([text UTF8String]));
+        self.message = @"";
+        if(sendPanelHeight.constant != SEND_PANEL_DEFAULT_HEIGHT) {
+            sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
+            messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
+            [self scrollToBottom];
+        }
+    }
+}
+
+- (IBAction)openEmojy:(id)sender {
+    [messageField.window makeFirstResponder: messageField];
+    [[messageField currentEditor] moveToEndOfLine:nil];
+    [NSApp orderFrontCharacterPalette: messageField];
+}
+
+- (IBAction)sendFile:(id)sender {
+    NSOpenPanel* filePicker = [NSOpenPanel openPanel];
+    [filePicker setCanChooseFiles:YES];
+    [filePicker setCanChooseDirectories:NO];
+    [filePicker setAllowsMultipleSelection:NO];
+
+    if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
+        if ([[filePicker URLs] count] == 1) {
+            NSURL* url = [[filePicker URLs] objectAtIndex:0];
+            const char* fullPath = [url fileSystemRepresentation];
+            NSString* fileName = [url lastPathComponent];
+            if (convModel_) {
+                auto* conv = [self getCurrentConversation];
+                convModel_->sendFile(convUid_, std::string(fullPath), std::string([fileName UTF8String]));
+            }
+        }
+    }
+}
+
+
+#pragma mark - NSTextFieldDelegate
+
+- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
+{
+    if (commandSelector == @selector(insertNewline:)) {
+        if(self.message.length > 0) {
+            [self sendMessage: nil];
+        } else if(messagesBottomMargin.constant != SEND_PANEL_DEFAULT_HEIGHT) {
+            sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
+            messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
+            [self scrollToBottom];
+        }
+        return YES;
+    }
+    return NO;
+}
+
+- (void)controlTextDidChange:(NSNotification *)aNotification {
+    [self updateSendMessageHeight];
+}
+
 @end