refactoring of messaging controller with new model

MessagesVC is now implemented using the new LRC model for
conversations.
 - Both views to display the messages (in call and off call)
   initialize their MessagesVC with the current conversation when
   needed.
 - A conversation caching system is introduced to not get the whole
   conversation::Info structure from LRC at each display request (once
   per message).

Change-Id: Ib520c1f88be78de37968d3d7741010f2c73f20ea
Reviewed-by: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
diff --git a/src/ChatVC.h b/src/ChatVC.h
index 17f63c6..6aa9fab 100644
--- a/src/ChatVC.h
+++ b/src/ChatVC.h
@@ -17,6 +17,8 @@
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
  */
 #import <Cocoa/Cocoa.h>
+#import <api/conversation.h>
+#import <api/conversationmodel.h>
 
 @interface ChatVC : NSViewController <NSTextFieldDelegate>
 
@@ -27,6 +29,7 @@
  */
 @property (retain) NSString* message;
 
+- (void) setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel*)model;
 - (void) takeFocus;
 
 @end
diff --git a/src/ChatVC.mm b/src/ChatVC.mm
index 7655f16..5a3a9ed 100644
--- a/src/ChatVC.mm
+++ b/src/ChatVC.mm
@@ -1,6 +1,7 @@
 /*
- *  Copyright (C) 2015-2016 Savoir-faire Linux Inc.
+ *  Copyright (C) 2015-2017 Savoir-faire Linux Inc.
  *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *  Author: Anthony Léonard <anthony.leonard@savoirfairelinux.com>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -19,45 +20,25 @@
 
 #import "ChatVC.h"
 
-#import <QItemSelectionModel>
-#import <qstring.h>
-
-#import <media/media.h>
-#import <media/text.h>
-#import <media/textrecording.h>
-#import <callmodel.h>
-
 #import "MessagesVC.h"
 
-@interface MediaConnectionsHolder : NSObject
-
-@property QMetaObject::Connection newMediaAdded;
-@property QMetaObject::Connection newMessage;
-
-@end
-
-@implementation MediaConnectionsHolder
-
-
-@end
-
-@interface ChatVC () <MessagesVCDelegate>
+@interface ChatVC ()
 {
- IBOutlet MessagesVC* messagesViewVC;
+    IBOutlet MessagesVC* messagesViewVC;
+
+    std::string convUid_;
+    lrc::api::ConversationModel* convModel_;
 }
 
 @property (unsafe_unretained) IBOutlet NSTextField *messageField;
 @property (unsafe_unretained) IBOutlet NSButton *sendButton;
 
-
-@property MediaConnectionsHolder* mediaHolder;
-
 @end
 
 @implementation ChatVC
 
 
-@synthesize messageField,sendButton, mediaHolder;
+@synthesize messageField,sendButton;
 
 - (void)awakeFromNib
 {
@@ -66,73 +47,14 @@
     [self.view setWantsLayer:YES];
     [self.view setLayer:[CALayer layer]];
     [self.view.layer setBackgroundColor:[NSColor controlColor].CGColor];
-
-    mediaHolder = [[MediaConnectionsHolder alloc] init];
-
-    QObject::connect(CallModel::instance().selectionModel(),
-                     &QItemSelectionModel::currentChanged,
-                     [=](const QModelIndex &current, const QModelIndex &previous) {
-                         [self setupChat];
-                     });
-    messagesViewVC.delegate = self;
 }
 
-
-- (void) setupChat
+-(void)setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel *)model
 {
-    QObject::disconnect(mediaHolder.newMediaAdded);
-    QObject::disconnect(mediaHolder.newMessage);
+    convUid_ = convUid;
+    convModel_ = model;
 
-    QModelIndex callIdx = CallModel::instance().selectionModel()->currentIndex();
-
-    if (!callIdx.isValid())
-        return;
-
-    Call* call = CallModel::instance().getCall(callIdx);
-
-    /* check if text media is already present */
-    if (call->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::IN)) {
-        Media::Text *text = call->firstMedia<Media::Text>(Media::Media::Direction::IN);
-        [self parseChatModel:text->recording()->instantMessagingModel()];
-    } else if (call->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::OUT)) {
-        Media::Text *text = call->firstMedia<Media::Text>(Media::Media::Direction::OUT);
-        [self parseChatModel:text->recording()->instantMessagingModel()];
-    } else {
-        /* monitor media for messaging text messaging */
-        mediaHolder.newMediaAdded = QObject::connect(call,
-                                                     &Call::mediaAdded,
-                                                     [self] (Media::Media* media) {
-                                                         if (media->type() == Media::Media::Type::TEXT) {
-                                                             QObject::disconnect(mediaHolder.newMediaAdded);
-                                                             [self parseChatModel:((Media::Text*)media)->recording()->instantMessagingModel()];
-                                                         }
-                                                     });
-    }
-}
-
-#pragma mark - MessagesVC delegate
-
--(void) newMessageAdded {
-
-    QModelIndex callIdx = CallModel::instance().selectionModel()->currentIndex();
-    if (!callIdx.isValid())
-        return;
-    Call* call = CallModel::instance().getCall(callIdx);
-    if (call->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::IN)) {
-        Media::Text *text = call->firstMedia<Media::Text>(Media::Media::Direction::IN);
-        auto textRecording = text->recording();
-        textRecording->setAllRead();
-    } else if (call->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::OUT)) {
-        Media::Text *text = call->firstMedia<Media::Text>(Media::Media::Direction::OUT);
-        auto textRecording = text->recording();
-        textRecording->setAllRead();
-    }
-}
-
-- (void) parseChatModel:(QAbstractItemModel *)model
-
-{
-    [messagesViewVC setUpViewWithModel:model];
+    [messagesViewVC setConversationUid:convUid_ model:convModel_];
 }
 
 - (void) takeFocus
@@ -141,19 +63,13 @@
 }
 
 - (IBAction)sendMessage:(id)sender {
-
-    QModelIndex callIdx = CallModel::instance().selectionModel()->currentIndex();
-    Call* call = CallModel::instance().getCall(callIdx);
-
     /* make sure there is text to send */
     NSString* text = self.message;
     if (text && text.length > 0) {
-        QMap<QString, QString> messages;
-        messages["text/plain"] = QString::fromNSString(text);
-        call->addOutgoingMedia<Media::Text>()->send(messages);
-        // Empty the text after sending it
-        [self.messageField setStringValue:@""];
+        convModel_->sendMessage(convUid_, std::string([text UTF8String]));
         self.message = @"";
+        [messageField setStringValue:@""];
+        [messagesViewVC newMessageSent];
     }
 }
 
diff --git a/src/ConversationVC.h b/src/ConversationVC.h
index cc170fe..887d8fb 100644
--- a/src/ConversationVC.h
+++ b/src/ConversationVC.h
@@ -18,6 +18,8 @@
  */
 
 #import <Cocoa/Cocoa.h>
+#import <api/conversation.h>
+#import <api/conversationmodel.h>
 
 @interface ConversationVC : NSViewController
 
@@ -32,4 +34,6 @@
  */
 @property (retain) NSString* message;
 
+- (void) setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel*)model;
+
 @end
diff --git a/src/ConversationVC.mm b/src/ConversationVC.mm
index 23cf157..0c8e503 100644
--- a/src/ConversationVC.mm
+++ b/src/ConversationVC.mm
@@ -1,6 +1,7 @@
 /*
- *  Copyright (C) 2016 Savoir-faire Linux Inc.
+ *  Copyright (C) 2016-2017 Savoir-faire Linux Inc.
  *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *  Author: Anthony Léonard <anthony.leonard@savoirfairelinux.com>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -24,13 +25,7 @@
 #import <QPixmap>
 #import <QtMacExtras/qmacfunctions.h>
 
-#import <media/media.h>
-#import <recentmodel.h>
-#import <person.h>
-#import <contactmethod.h>
-#import <media/text.h>
-#import <media/textrecording.h>
-#import <callmodel.h>
+// LRC
 #import <globalinstances.h>
 
 #import "views/IconButton.h"
@@ -43,22 +38,17 @@
 #import "account.h"
 #import "AvailableAccountModel.h"
 #import "MessagesVC.h"
-
+#import "utils.h"
 
 #import <QuartzCore/QuartzCore.h>
 
-@interface ConversationVC () <NSOutlineViewDelegate, MessagesVCDelegate> {
+@interface ConversationVC () {
 
     __unsafe_unretained IBOutlet NSTextField* messageField;
-    QVector<ContactMethod*> contactMethods;
     NSMutableString* textSelection;
 
-    QMetaObject::Connection contactMethodChanged;
-    ContactMethod* selectedContactMethod;
-
     __unsafe_unretained IBOutlet NSView* sendPanel;
     __unsafe_unretained IBOutlet NSTextField* conversationTitle;
-    __unsafe_unretained IBOutlet NSTextField* emptyConversationPlaceHolder;
     __unsafe_unretained IBOutlet IconButton* sendButton;
     __unsafe_unretained IBOutlet NSPopUpButton* contactMethodsPopupButton;
     __unsafe_unretained IBOutlet NSLayoutConstraint* sentContactRequestWidth;
@@ -67,6 +57,15 @@
 
     IBOutlet NSLayoutConstraint* titleHoverButtonConstraint;
     IBOutlet NSLayoutConstraint* titleTopConstraint;
+
+    std::string convUid_;
+    const lrc::api::conversation::Info* cachedConv_;
+    lrc::api::ConversationModel* convModel_;
+
+    // Both are needed to invalidate cached conversation as pointer
+    // may not be referencing the same conversation anymore
+    QMetaObject::Connection modelSortedSignal_;
+    QMetaObject::Connection filterChangedSignal_;
 }
 
 
@@ -74,6 +73,68 @@
 
 @implementation ConversationVC
 
+-(const lrc::api::conversation::Info*) getCurrentConversation
+{
+    if (convModel_ == nil || convUid_.empty())
+        return nil;
+
+    if (cachedConv_ != nil)
+        return cachedConv_;
+
+    auto& convQueue = convModel_->allFilteredConversations();
+
+    auto it = std::find_if(convQueue.begin(), convQueue.end(), [self](const lrc::api::conversation::Info& conv) {return conv.uid == convUid_;});
+
+    if (it != convQueue.end())
+        cachedConv_ = &(*it);
+
+    return cachedConv_;
+}
+
+-(void) setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel *)model {
+    if (convUid_ == convUid && convModel_ == model)
+        return;
+
+    cachedConv_ = nil;
+    convUid_ = convUid;
+    convModel_ = model;
+
+    [messagesViewVC setConversationUid:convUid_ model:convModel_];
+
+    if (convUid_.empty() || convModel_ == nil)
+        return;
+
+    // Signals tracking changes in conversation list, we need them as cached conversation can be invalid
+    // after a reordering.
+    QObject::disconnect(modelSortedSignal_);
+    QObject::disconnect(filterChangedSignal_);
+    modelSortedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelSorted,
+                                          [self](){
+                                              cachedConv_ = nil;
+                                          });
+    filterChangedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::filterChanged,
+                                            [self](){
+                                                cachedConv_ = nil;
+                                            });
+
+    auto* conv = [self getCurrentConversation];
+
+    if (conv == nil)
+        return;
+
+    // Setup UI elements according to new conversation
+    NSString* bestName = bestNameForConversation(*conv, *convModel_);
+    [conversationTitle setStringValue: bestName];
+
+    [contactMethodsPopupButton setEnabled:NO];
+    [contactMethodsPopupButton setBordered:NO];
+    BOOL hideCMPopupButton = [bestNameForConversation(*conv, *convModel_) isEqualTo:bestIDForConversation(*conv, *convModel_)];
+    [contactMethodsPopupButton setHidden:hideCMPopupButton];
+
+    [titleHoverButtonConstraint setActive:hideCMPopupButton];
+    [titleTopConstraint setActive:!hideCMPopupButton];
+}
+
 - (void)loadView {
     [super loadView];
     // Do view setup here.
@@ -83,9 +144,6 @@
     [self.view.layer setCornerRadius:5.0f];
 
     [messageField setFocusRingType:NSFocusRingTypeNone];
-
-    [self setupChat];
-
 }
 
 -(Account* ) chosenAccount
@@ -105,80 +163,26 @@
     self.view.layer.position = self.view.frame.origin;
 }
 
-- (void) setupChat
-{
-    QObject::connect(RecentModel::instance().selectionModel(),
-                     &QItemSelectionModel::currentChanged,
-                     [=](const QModelIndex &current, const QModelIndex &previous) {
-
-                         contactMethods = RecentModel::instance().getContactMethods(current);
-                         if (contactMethods.isEmpty()) {
-                             return ;
-                         }
-
-                         [contactMethodsPopupButton removeAllItems];
-                         for (auto cm : contactMethods) {
-                             [contactMethodsPopupButton addItemWithTitle:cm->bestId().toNSString()];
-                         }
-
-                         BOOL isSMultipleCM = (contactMethods.length() > 1);
-                         BOOL hideCMPopupButton = !isSMultipleCM && (contactMethods.first()->bestId() == contactMethods.first()->bestName());
-
-                         [contactMethodsPopupButton setEnabled:isSMultipleCM];
-                         [contactMethodsPopupButton setBordered:isSMultipleCM];
-                         [contactMethodsPopupButton setHidden:hideCMPopupButton];
-                         [[contactMethodsPopupButton cell] setArrowPosition: !isSMultipleCM ? NSPopUpNoArrow : NSPopUpArrowAtBottom];
-
-                         [titleHoverButtonConstraint setActive:hideCMPopupButton];
-                         [titleTopConstraint setActive:!hideCMPopupButton];
-
-                         [emptyConversationPlaceHolder setHidden:NO];
-                         // Select first cm
-                         [contactMethodsPopupButton selectItemAtIndex:0];
-                         [self itemChanged:contactMethodsPopupButton];
-                     });
-}
-
 - (IBAction)sendMessage:(id)sender
 {
+    auto* conv = [self getCurrentConversation];
     /* make sure there is text to send */
     NSString* text = self.message;
     if (text && text.length > 0) {
-        QMap<QString, QString> messages;
-        messages["text/plain"] = QString::fromNSString(text);
-        contactMethods.at([contactMethodsPopupButton indexOfSelectedItem])->sendOfflineTextMessage(messages);
+        convModel_->sendMessage(conv->uid, std::string([text UTF8String]));
         self.message = @"";
+        [messagesViewVC newMessageSent];
     }
 }
 
 - (IBAction)placeCall:(id)sender
 {
-    if(auto cm = contactMethods.at([contactMethodsPopupButton indexOfSelectedItem])) {
-        auto c = CallModel::instance().dialingCall();
-        c->setPeerContactMethod(cm);
-        c << Call::Action::ACCEPT;
-        CallModel::instance().selectCall(c);
-    }
+    auto* conv = [self getCurrentConversation];
+    convModel_->placeCall(conv->uid);
 }
 
 - (IBAction)backPressed:(id)sender {
-    RecentModel::instance().selectionModel()->clearCurrentIndex();
-    messagesViewVC.delegate = nil;
-}
-
-- (IBAction)sendContactRequest:(id)sender
-{
-    auto cm = contactMethods.at([contactMethodsPopupButton indexOfSelectedItem]);
-    if(cm) {
-        if(cm->account() == nullptr) {
-            cm->setAccount([self chosenAccount]);
-        }
-
-        if(cm->account() == nullptr) {
-            return;
-        }
-        cm->account()->sendContactRequest(cm);
-    }
+    [self animateOut];
 }
 
 # pragma mark private IN/OUT animations
@@ -233,65 +237,5 @@
     return NO;
 }
 
--(BOOL)shouldHideSendRequestBtn {
-    /*to send contact request we need to meet thre condition:
-     1)contact method has RING protocol
-     2)accound is used to send request is also RING
-     3)contact  have not acceppt request yet*/
-    if(selectedContactMethod->protocolHint() != URI::ProtocolHint::RING) {
-        return YES;
-    }
-    if(selectedContactMethod->isConfirmed()) {
-        return YES;
-    }
-    if(selectedContactMethod->account()) {
-        return selectedContactMethod->account()->protocol() != Account::Protocol::RING;
-    }
-    if([self chosenAccount]) {
-        return [self chosenAccount]->protocol() != Account::Protocol::RING;
-    }
-    return NO;
-}
-
--(void)updateSendButtonVisibility
-{
-    [sentContactRequestButton setHidden:[self shouldHideSendRequestBtn]];
-    sentContactRequestWidth.priority = [self shouldHideSendRequestBtn] ? 999: 250;
-}
-
-#pragma mark - NSPopUpButton item selection
-
-- (IBAction)itemChanged:(id)sender {
-    NSInteger index = [(NSPopUpButton *)sender indexOfSelectedItem];
-
-    selectedContactMethod = contactMethods.at(index);
-
-    [self updateSendButtonVisibility];
-
-    [conversationTitle setStringValue:selectedContactMethod->bestName().toNSString()];
-    QObject::disconnect(contactMethodChanged);
-    contactMethodChanged = QObject::connect(selectedContactMethod,
-                                            &ContactMethod::changed,
-                                            [self] {
-                                                [conversationTitle setStringValue:selectedContactMethod->bestName().toNSString()];
-                                                [self updateSendButtonVisibility];
-                                            });
-
-    if (auto txtRecording = selectedContactMethod->textRecording()) {
-        messagesViewVC.delegate = self;
-        [messagesViewVC setUpViewWithModel:txtRecording->instantMessagingModel()];
-        [self.view.window makeFirstResponder:messageField];
-    }
-}
-
-#pragma mark - MessagesVC delegate
-
--(void) newMessageAdded {
-
-    if (auto txtRecording = contactMethods.at([contactMethodsPopupButton indexOfSelectedItem])->textRecording()) {
-        [emptyConversationPlaceHolder setHidden:txtRecording->instantMessagingModel()->rowCount() > 0];
-        txtRecording->setAllRead();
-    }
-}
 
 @end
diff --git a/src/MessagesVC.h b/src/MessagesVC.h
index 6b5430f..f92e75a 100644
--- a/src/MessagesVC.h
+++ b/src/MessagesVC.h
@@ -18,6 +18,8 @@
  */
 
 #import <Cocoa/Cocoa.h>
+#import <api/conversationmodel.h>
+#import <api/conversation.h>
 
 @protocol MessagesVCDelegate
 
@@ -27,7 +29,9 @@
 
 @interface MessagesVC : NSViewController
 
--(void)setUpViewWithModel: (QAbstractItemModel*) model;
+-(void)setConversationUid:(const std::string)convUid model:(const lrc::api::ConversationModel*)model;
+-(void)newMessageSent;
+
 @property (retain, nonatomic) id <MessagesVCDelegate> delegate;
 
 @end
diff --git a/src/MessagesVC.mm b/src/MessagesVC.mm
index a44ba20..035c4f8 100644
--- a/src/MessagesVC.mm
+++ b/src/MessagesVC.mm
@@ -1,6 +1,7 @@
 /*
  *  Copyright (C) 2015-2017 Savoir-faire Linux Inc.
  *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *          Anthony Léonard <anthony.leonard@savoirfairelinux.com>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -18,27 +19,33 @@
  */
 
 #import <QItemSelectionModel>
-#import <qstring.h>
 #import <QPixmap>
 #import <QtMacExtras/qmacfunctions.h>
 
-#import <media/media.h>
-#import <person.h>
-#import <media/text.h>
-#import <media/textrecording.h>
+// LRC
 #import <globalinstances.h>
+#import <api/interaction.h>
 
 #import "MessagesVC.h"
-#import "QNSTreeController.h"
 #import "views/IMTableCellView.h"
 #import "views/MessageBubbleView.h"
 #import "INDSequentialTextSelectionManager.h"
+#import "delegates/ImageManipulationDelegate.h"
 
-@interface MessagesVC () {
+@interface MessagesVC () <NSTableViewDelegate, NSTableViewDataSource> {
 
-    QNSTreeController* treeController;
-    __unsafe_unretained IBOutlet NSOutlineView* conversationView;
+    __unsafe_unretained IBOutlet NSTableView* conversationView;
 
+    std::string convUid_;
+    const lrc::api::ConversationModel* convModel_;
+    const lrc::api::conversation::Info* cachedConv_;
+
+    QMetaObject::Connection newMessageSignal_;
+
+    // Both are needed to invalidate cached conversation as pointer
+    // may not be referencing the same conversation anymore
+    QMetaObject::Connection modelSortedSignal_;
+    QMetaObject::Connection filterChangedSignal_;
 }
 
 @property (nonatomic, strong, readonly) INDSequentialTextSelectionManager* selectionManager;
@@ -46,107 +53,221 @@
 @end
 
 @implementation MessagesVC
-QAbstractItemModel* currentModel;
 
--(void)setUpViewWithModel: (QAbstractItemModel*) model {
+-(const lrc::api::conversation::Info*) getCurrentConversation
+{
+    if (convModel_ == nil || convUid_.empty())
+        return nil;
 
-     _selectionManager = [[INDSequentialTextSelectionManager alloc] init];
+    if (cachedConv_ != nil)
+        return cachedConv_;
 
-    [self.selectionManager unregisterAllTextViews];
+    auto& convQueue = convModel_->allFilteredConversations();
 
-    treeController = [[QNSTreeController alloc] initWithQModel:model];
-    [treeController setAvoidsEmptySelection:NO];
-    [treeController setChildrenKeyPath:@"children"];
-    [conversationView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
-    [conversationView bind:@"sortDescriptors" toObject:treeController withKeyPath:@"sortDescriptors" options:nil];
-    [conversationView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];
+    auto it = std::find_if(convQueue.begin(), convQueue.end(), [self](const lrc::api::conversation::Info& conv) {return conv.uid == convUid_;});
+
+    if (it != convQueue.end())
+        cachedConv_ = &(*it);
+
+    return cachedConv_;
+}
+
+-(void)setConversationUid:(const std::string)convUid model:(const lrc::api::ConversationModel *)model
+{
+    if (convUid_ == convUid && convModel_ == model)
+        return;
+
+    cachedConv_ = nil;
+    convUid_ = convUid;
+    convModel_ = model;
+
+    // Signal triggered when messages are received
+    QObject::disconnect(newMessageSignal_);
+    newMessageSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::newUnreadMessage,
+                                         [self](const std::string& uid, uint64_t msgId, const lrc::api::interaction::Info& msg){
+                                             if (uid != convUid_)
+                                                 return;
+                                             [conversationView reloadData];
+                                             [conversationView scrollToEndOfDocument:nil];
+                                         });
+
+    // Signals tracking changes in conversation list, we need them as cached conversation can be invalid
+    // after a reordering.
+    QObject::disconnect(modelSortedSignal_);
+    QObject::disconnect(filterChangedSignal_);
+    modelSortedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelSorted,
+                                          [self](){
+                                              cachedConv_ = nil;
+                                          });
+    filterChangedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::filterChanged,
+                                          [self](){
+                                              cachedConv_ = nil;
+                                          });
+
+
+
+    [conversationView reloadData];
     [conversationView scrollToEndOfDocument:nil];
-    currentModel = model;
 }
 
-#pragma mark - NSOutlineViewDelegate methods
+-(void)newMessageSent
+{
+    [conversationView reloadData];
+    [conversationView scrollToEndOfDocument:nil];
+}
 
-- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
+#pragma mark - NSTableViewDelegate methods
+- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
 {
     return YES;
 }
 
-- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
+- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
 {
-    return YES;
+    return NO;
 }
 
-- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
+- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
 {
-    QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
-    if(!qIdx.isValid()) {
-        return [outlineView makeViewWithIdentifier:@"LeftMessageView" owner:self];
+    auto* conv = [self getCurrentConversation];
+
+    if (conv == nil)
+        return nil;
+
+    // HACK HACK HACK HACK HACK
+    // The following code has to be replaced when every views are implemented for every interaction types
+    // This is an iterator which "jumps over" any interaction which is not a text one.
+    // It behaves as if interaction list was only containing text interactions.
+    std::map<uint64_t, lrc::api::interaction::Info>::const_iterator it;
+
+    {
+        int msgCount = 0;
+        it = std::find_if(conv->interactions.begin(), conv->interactions.end(), [&msgCount, row](const std::pair<uint64_t, lrc::api::interaction::Info>& inter) {
+            if (inter.second.type == lrc::api::interaction::Type::TEXT) {
+                if (msgCount == row) {
+                    return true;
+                } else {
+                    msgCount++;
+                    return false;
+                }
+            }
+            return false;
+        });
     }
-    auto dir = qvariant_cast<Media::Media::Direction>(qIdx.data((int)Media::TextRecording::Role::Direction));
+
+    if (it == conv->interactions.end())
+        return nil;
+
     IMTableCellView* result;
 
-    if (dir == Media::Media::Direction::IN) {
-        result = [outlineView makeViewWithIdentifier:@"LeftMessageView" owner:self];
+    auto& interaction = it->second;
+
+    // TODO Implement interactions other than messages
+    if(interaction.type != lrc::api::interaction::Type::TEXT) {
+        return nil;
+    }
+
+    bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
+
+    if (isOutgoing) {
+        result = [tableView makeViewWithIdentifier:@"RightMessageView" owner:self];
     } else {
-        result = [outlineView makeViewWithIdentifier:@"RightMessageView" owner:self];
+        result = [tableView makeViewWithIdentifier:@"LeftMessageView" owner:self];
     }
 
     // check if the message first in incoming or outgoing messages sequence
     Boolean isFirstInSequence = true;
-    int row = qIdx.row() - 1;
-    if(row >= 0) {
-        QModelIndex index = currentModel->index(row, 0);
-        if(index.isValid()) {
-            auto dirOld = qvariant_cast<Media::Media::Direction>(index.data((int)Media::TextRecording::Role::Direction));
-            isFirstInSequence = !(dirOld == dir);
-        }
+    if (it != conv->interactions.begin()) {
+        auto previousIt = it;
+        previousIt--;
+        auto& previousInteraction = previousIt->second;
+        if (previousInteraction.type == lrc::api::interaction::Type::TEXT && (isOutgoing == lrc::api::interaction::isOutgoing(previousInteraction)))
+            isFirstInSequence = false;
     }
     [result.photoView setHidden:!isFirstInSequence];
     result.msgBackground.needPointer = isFirstInSequence;
     [result setup];
 
     NSMutableAttributedString* msgAttString =
-    [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",qIdx.data((int)Qt::DisplayRole).toString().toNSString()]
+    [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",@(interaction.body.c_str())]
                                            attributes:[self messageAttributes]];
 
+    NSDate *msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
     NSAttributedString* timestampAttrString =
-    [[NSAttributedString alloc] initWithString:qIdx.data((int)Media::TextRecording::Role::FormattedDate).toString().toNSString()
+    [[NSAttributedString alloc] initWithString:[NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle]
                                     attributes:[self timestampAttributes]];
 
-
     CGFloat finalWidth = MAX(msgAttString.size.width, timestampAttrString.size.width);
 
-    finalWidth = MIN(finalWidth + 30, outlineView.frame.size.width * 0.7);
-
-    NSString* msgString = qIdx.data((int)Qt::DisplayRole).toString().toNSString();
-    NSString* dateString = qIdx.data((int)Qt::DisplayRole).toString().toNSString();
+    finalWidth = MIN(finalWidth + 30, tableView.frame.size.width * 0.7);
 
     [msgAttString appendAttributedString:timestampAttrString];
     [[result.msgView textStorage] appendAttributedString:msgAttString];
     [result.msgView checkTextInDocument:nil];
     [result updateWidthConstraint:finalWidth];
-    [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(qIdx.data(Qt::DecorationRole)))];
+
+    auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
+    [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
     return result;
 }
 
-- (void)outlineView:(NSOutlineView *)outlineView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
+- (void)tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
 {
-    if (IMTableCellView* cellView = [outlineView viewAtColumn:0 row:row makeIfNecessary:NO]) {
+    if (IMTableCellView* cellView = [tableView viewAtColumn:0 row:row makeIfNecessary:NO]) {
         [self.selectionManager registerTextView:cellView.msgView withUniqueIdentifier:@(row).stringValue];
     }
-    [self.delegate newMessageAdded];
 }
 
-- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
+- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
 {
-    QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
-    double someWidth = outlineView.frame.size.width * 0.7;
+    double someWidth = tableView.frame.size.width * 0.7;
 
-    NSMutableAttributedString* msgAttString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",qIdx.data((int)Qt::DisplayRole).toString().toNSString()]
-                                                                                     attributes:[self messageAttributes]];
-    NSAttributedString *timestampAttrString = [[NSAttributedString alloc] initWithString:
-                                               qIdx.data((int)Media::TextRecording::Role::FormattedDate).toString().toNSString()
-                                                                              attributes:[self timestampAttributes]];
+    auto* conv = [self getCurrentConversation];
+
+    if (conv == nil)
+        return 0;
+
+    // HACK HACK HACK HACK HACK
+    // The following code has to be replaced when every views are implemented for every interaction types
+    // This is an iterator which "jumps over" any interaction which is not a text one.
+    // It behaves as if interaction list was only containing text interactions.
+    std::map<uint64_t, lrc::api::interaction::Info>::const_iterator it;
+
+    {
+        int msgCount = 0;
+        it = std::find_if(conv->interactions.begin(), conv->interactions.end(), [&msgCount, row](const std::pair<uint64_t, lrc::api::interaction::Info>& inter) {
+            if (inter.second.type == lrc::api::interaction::Type::TEXT) {
+                if (msgCount == row) {
+                    return true;
+                } else {
+                    msgCount++;
+                    return false;
+                }
+            }
+            return false;
+        });
+    }
+
+    if (it == conv->interactions.end())
+        return 0;
+
+    auto& interaction = it->second;
+
+    // TODO Implement interactions other than messages
+    if(interaction.type != lrc::api::interaction::Type::TEXT) {
+        return 0;
+    }
+
+    NSMutableAttributedString* msgAttString =
+    [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",@(interaction.body.c_str())]
+                                           attributes:[self messageAttributes]];
+
+    NSDate *msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
+    NSAttributedString* timestampAttrString =
+    [[NSAttributedString alloc] initWithString:[NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle]
+                                    attributes:[self timestampAttributes]];
+
+    [msgAttString appendAttributedString:timestampAttrString];
 
     [msgAttString appendAttributedString:timestampAttrString];
 
@@ -161,6 +282,29 @@
     return MAX(height, 50.0f);
 }
 
+#pragma mark - NSTableViewDataSource
+
+- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
+{
+    auto* conv = [self getCurrentConversation];
+
+    if (conv) {
+        int count;
+        count = std::count_if(conv->interactions.begin(), conv->interactions.end(), [](const std::pair<uint64_t, lrc::api::interaction::Info>& inter) {
+            return inter.second.type == lrc::api::interaction::Type::TEXT;
+        });
+        return count;
+    }
+    return 0;
+
+#if 0
+    // TODO: Replace above code by the following one when every interaction types implemented
+    if (conv_) {
+        return conv_->interactions.size();
+    }
+#endif
+}
+
 #pragma mark - Text formatting
 
 - (NSMutableDictionary*) timestampAttributes
diff --git a/src/RingWindowController.mm b/src/RingWindowController.mm
index 1d51d5a..0afba58 100644
--- a/src/RingWindowController.mm
+++ b/src/RingWindowController.mm
@@ -147,6 +147,16 @@
                          [currentCallVC animateIn];
                          [offlineVC animateOut];
                      });
+
+    QObject::connect(&lrc_->getBehaviorController(),
+                     &lrc::api::BehaviorController::showChatView,
+                     [self](const std::string& accountId,
+                            const lrc::api::conversation::Info& convInfo){
+                         auto& accInfo = lrc_->getAccountModel().getAccountInfo(accountId);
+                         [offlineVC setConversationUid:convInfo.uid model:accInfo.conversationModel.get()];
+                         [offlineVC animateIn];
+                         [currentCallVC animateOut];
+                     });
 }
 
 /**