conversation: support files drag and drop

Change-Id: If97d5b8602ab162b605b1df02c9528d6609d4f0c
diff --git a/src/MessagesVC.mm b/src/MessagesVC.mm
index ce0c926..9ecbd65 100644
--- a/src/MessagesVC.mm
+++ b/src/MessagesVC.mm
@@ -30,6 +30,7 @@
 #import "views/IMTableCellView.h"
 #import "views/MessageBubbleView.h"
 #import "views/NSImage+Extensions.h"
+#import "views/FileToSendCollectionItem.h"
 #import "delegates/ImageManipulationDelegate.h"
 #import "utils.h"
 #import "views/NSColor+RingTheme.h"
@@ -41,10 +42,14 @@
 
 #import "RecordFileVC.h"
 
+@implementation PendingFile
+@end
 
-@interface MessagesVC () <NSTableViewDelegate, NSTableViewDataSource, QLPreviewPanelDataSource, NSTextViewDelegate> {
+@interface MessagesVC () <NSTableViewDelegate, NSTableViewDataSource, QLPreviewPanelDataSource, NSTextViewDelegate, NSCollectionViewDataSource> {
 
     __unsafe_unretained IBOutlet NSTableView* conversationView;
+    __unsafe_unretained IBOutlet DraggingDestinationView* draggingDestinationView;
+    __unsafe_unretained IBOutlet NSCollectionView* pendingFilesCollectionView;
     __unsafe_unretained IBOutlet NSView* containerView;
     __unsafe_unretained IBOutlet TextViewWithPlaceholder* messageView;
     __unsafe_unretained IBOutlet IconButton *sendFileButton;
@@ -52,7 +57,6 @@
     __unsafe_unretained IBOutlet IconButton *recordAudioButton;
     __unsafe_unretained IBOutlet NSLayoutConstraint* sendPanelHeight;
     __unsafe_unretained IBOutlet NSLayoutConstraint* messageHeight;
-    __unsafe_unretained IBOutlet NSLayoutConstraint* messagesBottomMargin;
     __unsafe_unretained IBOutlet NSLayoutConstraint* textBottomConstraint;
     IBOutlet NSPopover *recordMessagePopover;
 
@@ -71,7 +75,7 @@
     QMetaObject::Connection lastDisplayedChanged_;
     NSString* previewImage;
     NSMutableDictionary *pendingMessagesToSend;
-    RecordFileVC * recordingController;
+    RecordFileVC* recordingController;
 }
 
 @end
@@ -126,6 +130,25 @@
     [[conversationView.enclosingScrollView contentView] setCopiesOnScroll:NO];
     [messageView setFont: [NSFont systemFontOfSize: 14 weight: NSFontWeightLight]];
     [conversationView setWantsLayer:YES];
+    draggingDestinationView.draggingDestinationDelegate = self;
+}
+
+-(void)callFinished {
+    [self reloadPendingFiles];
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [conversationView scrollToEndOfDocument:nil];
+        [messageView.window makeFirstResponder: messageView];
+    });
+}
+
++(NSMutableDictionary*)pendingFiles
+{
+    static NSMutableDictionary* files = nil;
+    static dispatch_once_t oncePredicate;
+    dispatch_once(&oncePredicate, ^{
+        files = [[NSMutableDictionary alloc] init];
+    });
+    return files;
 }
 
 - (instancetype)initWithCoder:(NSCoder *)coder
@@ -341,25 +364,10 @@
     dispatch_async(dispatch_get_main_queue(), ^{
         [messageView.window makeFirstResponder: messageView];
     });
-    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;
-    try {
-        [sendFileButton setEnabled:(convModel_->owner.contactModel->getContact(conv->participants[0]).profileInfo.type != lrc::api::profile::Type::SIP)];
-    } catch (std::out_of_range& e) {
-        NSLog(@"contact out of range");
-    }
     NSString* name = bestNameForConversation(*conv, *convModel_);
     NSString *placeholder = [NSString stringWithFormat:@"%@%@", @"Write to ", name];
 
@@ -371,6 +379,22 @@
                                nil];
     NSAttributedString* attributedPlaceholder = [[NSAttributedString alloc] initWithString: placeholder attributes:nameAttrs];
     messageView.placeholderAttributedString = attributedPlaceholder;
+    [self reloadPendingFiles];
+    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;
+    try {
+        [sendFileButton setEnabled:(convModel_->owner.contactModel->getContact(conv->participants[0]).profileInfo.type != lrc::api::profile::Type::SIP)];
+    } catch (std::out_of_range& e) {
+        NSLog(@"contact out of range");
+    }
 }
 
 #pragma mark - configure cells
@@ -960,7 +984,6 @@
     if (MESSAGE_VIEW_DEFAULT_HEIGHT != lineHeight) {
         MESSAGE_VIEW_DEFAULT_HEIGHT = lineHeight;
     }
-    messagesBottomMargin.constant = newSendPanelHeight;
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
         [self scrollToBottom];
         messageHeight.constant = msgHeight;
@@ -1068,6 +1091,14 @@
     unichar separatorChar = NSLineSeparatorCharacter;
     NSString *separatorString = [NSString stringWithCharacters:&separatorChar length:1];
     text = [text stringByReplacingOccurrencesOfString: separatorString withString: @"\n"];
+    // send files
+    NSMutableArray* files = [MessagesVC pendingFiles][convUid_.toNSString()];
+    for (PendingFile* file : files) {
+        convModel_->sendFile(convUid_, QString::fromNSString(file.fileUrl.path), QString::fromNSString(file.name));
+    }
+    [MessagesVC.pendingFiles removeObjectForKey: convUid_.toNSString()];
+    [self reloadPendingFiles];
+
     if (text && text.length > 0) {
         auto* conv = [self getCurrentConversation];
         if (conv == nil)
@@ -1086,7 +1117,6 @@
     if(messageHeight.constant != MESSAGE_VIEW_DEFAULT_HEIGHT) {
         sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
         messageHeight.constant = MESSAGE_VIEW_DEFAULT_HEIGHT;
-        messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
         textBottomConstraint.constant = BOTTOM_MARGIN;
         [self scrollToBottom];
     }
@@ -1143,18 +1173,10 @@
     NSOpenPanel* filePicker = [NSOpenPanel openPanel];
     [filePicker setCanChooseFiles:YES];
     [filePicker setCanChooseDirectories:NO];
-    [filePicker setAllowsMultipleSelection:NO];
+    [filePicker setAllowsMultipleSelection:YES];
 
     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_, QString::fromStdString(fullPath), QString::fromNSString(fileName));
-            }
-        }
+        [self filesDragged: [filePicker URLs]];
     }
 }
 
@@ -1163,7 +1185,7 @@
 
 - (BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector {
     if (commandSelector == @selector(insertNewline:)) {
-        if(self.message.length > 0) {
+        if(self.message.length > 0 || [(NSMutableArray*)MessagesVC. pendingFiles[convUid_.toNSString()] count] > 0) {
             [self sendMessage: nil];
             return YES;
         }
@@ -1173,8 +1195,9 @@
     return NO;
 }
 
--(void) textDidChange:(NSNotification *)notification {
+-(void)textDidChange:(NSNotification *)notification {
     [self checkIfComposingMsg];
+    self.enableSendButton = self.message.length > 0 || [(NSMutableArray*)MessagesVC. pendingFiles[convUid_.toNSString()] count] > 0;
 }
 
 - (void) checkIfComposingMsg {
@@ -1226,7 +1249,6 @@
     }
 }
 
-
 - (void)removeFromResponderChain {
     if (conversationView.window &&
         [[conversationView.window nextResponder] isEqual:self]) {
@@ -1235,4 +1257,78 @@
     }
 }
 
+#pragma mark - NSCollectionViewDataSource
+
+- (NSInteger)collectionView:(NSCollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
+    return [(NSMutableArray*)MessagesVC.pendingFiles[convUid_.toNSString()] count];
+}
+- (NSCollectionViewItem*)collectionView:(NSCollectionView *)collectionView itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {
+    FileToSendCollectionItem* fileCell = [collectionView makeItemWithIdentifier:@"FileToSendCollectionItem" forIndexPath:indexPath];
+    PendingFile* file = MessagesVC.pendingFiles[convUid_.toNSString()][indexPath.item];
+    fileCell.filePreview.image = file.preview;
+    fileCell.fileName.stringValue = file.name;
+    fileCell.fileName.toolTip = file.name;
+    fileCell.fileSize.stringValue = file.size;
+    [fileCell.closeButton setAction:@selector(removePendingFile:)];
+    fileCell.closeButton.tag = indexPath.item;
+    [fileCell.closeButton setTarget:self];
+    return fileCell;
+}
+
+#pragma mark - DraggingDestinationDelegate
+
+-(void)filesDragged:(NSArray*)urls {
+    [self prepareFilesToSend: urls];
+}
+
+-(NSString*)convertBytedToString:(double)bytes {
+    if (bytes <= 1000) {
+        return [NSString stringWithFormat:@"%.2f%@", bytes, @" B"];
+    } else if (bytes <= 1e6) {
+        return [NSString stringWithFormat:@"%.2f%@",(bytes * 1e-3), @" KB"];
+    } else if (bytes <= 1e9) {
+       return [NSString stringWithFormat:@"%.2f%@",(bytes * 1e-6), @" MB"];
+    }
+    return [NSString stringWithFormat:@"%.2f%@",(bytes * 1e-9), @" GB"];
+}
+
+-(void)prepareFilesToSend:(NSArray*)urls {
+    NSMutableArray* files = [[NSMutableArray alloc] init];
+    NSMutableArray* existingFiles = MessagesVC.pendingFiles[convUid_.toNSString()];
+    [files addObjectsFromArray: existingFiles];
+    for (NSURL* url : urls) {
+        NSString* filePath = [url path];
+        NSImage* preview = [[NSImage alloc] initWithContentsOfFile: filePath];
+        NSString* name = [url lastPathComponent];
+        NSData* documentBytes = [[NSData alloc] initWithContentsOfFile: filePath];
+        PendingFile* file = [[PendingFile alloc] init];
+        file.name = name;
+        file.size = [self convertBytedToString: documentBytes.length];
+        file.preview = preview;
+        file.fileUrl = url;
+        [files addObject: file];
+    }
+    MessagesVC.pendingFiles[convUid_.toNSString()] = files;
+    [self reloadPendingFiles];
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+        [self scrollToBottom];
+    });
+}
+
+- (void)removePendingFile:(id)sender {
+    NSButton* closeButton = (NSButton*)sender;
+    NSInteger index = closeButton.tag;
+    if(index < 0) {
+        return;
+    }
+    [MessagesVC.pendingFiles[convUid_.toNSString()] removeObjectAtIndex:index];
+    [self reloadPendingFiles];
+}
+
+- (void)reloadPendingFiles {
+    self.hideFilesCollection = [(NSMutableArray*)MessagesVC .pendingFiles[convUid_.toNSString()] count]== 0;
+    self.enableSendButton = self.message.length > 0 || [(NSMutableArray*)MessagesVC. pendingFiles[convUid_.toNSString()] count] > 0;
+    [pendingFilesCollectionView reloadData];
+}
+
 @end