Commit 4f37d957 authored by Kateryna Kostiuk's avatar Kateryna Kostiuk Committed by Andreas Traczyk

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's avatarAndreas Traczyk <andreas.traczyk@savoirfairelinux.com>
parent 512db882
......@@ -29,13 +29,6 @@
-(void) showWithAnimation:(BOOL)animate;
-(void) hideWithAnimation:(BOOL)animate;
/**
* Message contained in messageField TextField.
* This is a KVO method to bind the text with the send Button
* if message.length is > 0, button is enabled, otherwise disabled
*/
@property (retain) NSString* message;
- (void) setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel*)model;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil delegate:(RingWindowController*) mainWindow;
......
......@@ -20,7 +20,6 @@
#import "ConversationVC.h"
#import <QItemSelectionModel>
#import <qstring.h>
#import <QPixmap>
#import <QtMacExtras/qmacfunctions.h>
......@@ -32,12 +31,7 @@
#import "views/HoverButton.h"
#import "views/IMTableCellView.h"
#import "views/NSColor+RingTheme.h"
#import "QNSTreeController.h"
#import "INDSequentialTextSelectionManager.h"
#import "delegates/ImageManipulationDelegate.h"
#import "PhoneDirectoryModel.h"
#import "account.h"
#import "AvailableAccountModel.h"
#import "MessagesVC.h"
#import "utils.h"
#import "RingWindowController.h"
......@@ -47,16 +41,11 @@
@interface ConversationVC () {
__unsafe_unretained IBOutlet NSTextField* messageField;
NSMutableString* textSelection;
__unsafe_unretained IBOutlet NSView* sendPanel;
__unsafe_unretained IBOutlet NSTextField* conversationTitle;
__unsafe_unretained IBOutlet NSTextField *conversationID;
__unsafe_unretained IBOutlet IconButton* sendButton;
__unsafe_unretained IBOutlet IconButton *sendFileButton;
__unsafe_unretained IBOutlet HoverButton *addContactButton;
__unsafe_unretained IBOutlet NSLayoutConstraint* sentContactRequestWidth;
__unsafe_unretained IBOutlet NSButton* sentContactRequestButton;
IBOutlet MessagesVC* messagesViewVC;
......@@ -72,12 +61,14 @@
// All those connections are needed to invalidate cached conversation as pointer
// may not be referencing the same conversation anymore
QMetaObject::Connection modelSortedConnection_, filterChangedConnection_, newConversationConnection_, conversationRemovedConnection_;
}
@end
NSInteger const MEESAGE_MARGIN = 21;
NSInteger const SEND_PANEL_DEFAULT_HEIGHT = 60;
NSInteger const SEND_PANEL_MAX_HEIGHT = 120;
@implementation ConversationVC
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil delegate:(RingWindowController*) mainWindow
......@@ -89,10 +80,6 @@
return self;
}
- (void)setMessage:(NSString *)newValue {
_message = [newValue removeEmptyLinesAtBorders];
}
-(void) clearData {
cachedConv_ = nil;
convUid_ = "";
......@@ -169,7 +156,6 @@
NSString* bestId = bestIDForConversation(*conv, *convModel_);
[conversationTitle setStringValue: bestName];
[conversationID setStringValue: bestId];
[sendFileButton setEnabled:(convModel_->owner.contactModel->getContact(conv->participants[0]).profileInfo.type != lrc::api::profile::Type::SIP)];
BOOL hideBestId = [bestNameForConversation(*conv, *convModel_) isEqualTo:bestIDForConversation(*conv, *convModel_)];
......@@ -180,21 +166,6 @@
[addContactButton setHidden:((convModel_->owner.contactModel->getContact(conv->participants[0]).profileInfo.type != lrc::api::profile::Type::TEMPORARY) || accountType == lrc::api::profile::Type::SIP)];
}
- (void)loadView {
[super loadView];
[messageField setFocusRingType:NSFocusRingTypeNone];
}
-(Account* ) chosenAccount
{
QModelIndex index = AvailableAccountModel::instance().selectionModel()->currentIndex();
if(!index.isValid()) {
return nullptr;
}
Account* account = index.data(static_cast<int>(Account::Role::Object)).value<Account*>();
return account;
}
- (void) initFrame
{
[self.view setFrame:self.view.superview.bounds];
......@@ -202,45 +173,6 @@
self.view.layer.position = self.view.frame.origin;
}
- (IBAction)sendMessage:(id)sender
{
/* make sure there is text to send */
NSString* text = self.message;
if (text && text.length > 0) {
auto* conv = [self getCurrentConversation];
bool isPending = convModel_->owner.contactModel->getContact(conv->participants[0]).profileInfo.type == lrc::api::profile::Type::PENDING;
convModel_->sendMessage(convUid_, std::string([text UTF8String]));
self.message = @"";
if (isPending)
[delegate currentConversationTrusted];
}
}
- (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];
bool isPending = convModel_->owner.contactModel->getContact(conv->participants[0]).profileInfo.type == lrc::api::profile::Type::PENDING;
convModel_->sendFile(convUid_, std::string(fullPath), std::string([fileName UTF8String]));
if (isPending)
[delegate currentConversationTrusted];
}
}
}
}
- (IBAction)placeCall:(id)sender
{
auto* conv = [self getCurrentConversation];
......@@ -317,16 +249,5 @@
[CATransaction commit];
}
#pragma mark - NSTextFieldDelegate
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
{
if (commandSelector == @selector(insertNewline:) && self.message.length > 0) {
[self sendMessage:nil];
return YES;
}
return NO;
}
@end
......@@ -39,6 +39,7 @@ class Call;
}
@property (retain, nonatomic) id <CallViewControllerDelegate> delegate;
-(void) initFrame;
-(void) cleanUp;
-(void) showWithAnimation:(BOOL)animate;
-(void) hideWithAnimation:(BOOL)animate;
-(void) setCurrentCall:(const std::string&)callUid
......
......@@ -339,10 +339,6 @@
case Status::ENDED:
case Status::TERMINATING:
case Status::INVALID:
[controlsPanel setHidden:YES];
[outgoingPanel setHidden:NO];
[self cleanUp];
[self hideWithAnimation:false];
break;
}
}
......
......@@ -21,17 +21,16 @@
#import <api/conversationmodel.h>
#import <api/conversation.h>
@protocol MessagesVCDelegate
-(void)newMessageAdded;
@end
@interface MessagesVC : NSViewController
-(void)setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel*)model;
-(void)clearData;
@property (retain, nonatomic) id <MessagesVCDelegate> delegate;
/**
* Message contained in messageField TextField.
* This is a KVO method to bind the text with the send Button
* if message.length is > 0, button is enabled, otherwise disabled
*/
@property (retain) NSString* message;
@end
......@@ -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 TIME_BOX_HEIGHT = 34;
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 @@ typedef NS_ENUM(NSInteger, MessageSequencing) {
[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 @@ typedef NS_ENUM(NSInteger, MessageSequencing) {
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 @@ typedef NS_ENUM(NSInteger, MessageSequencing) {
[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 @@ typedef NS_ENUM(NSInteger, MessageSequencing) {
[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 @@ typedef NS_ENUM(NSInteger, MessageSequencing) {
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
......@@ -125,6 +125,7 @@ NSString* const kOpenAccountToolBarItemIdentifier = @"OpenAccountToolBarItemI
[self accountSettingsShouldOpen: NO];
[conversationVC hideWithAnimation:false];
[currentCallVC hideWithAnimation:false];
[currentCallVC cleanUp];
[currentCallVC.view removeFromSuperview];
[welcomeContainer setHidden: NO];
[smartViewVC.view setHidden: NO];
......@@ -134,6 +135,7 @@ NSString* const kOpenAccountToolBarItemIdentifier = @"OpenAccountToolBarItemI
[self accountSettingsShouldOpen: NO];
[conversationVC showWithAnimation:false];
[currentCallVC hideWithAnimation:false];
[currentCallVC cleanUp];
[currentCallVC.view removeFromSuperview];
[welcomeContainer setHidden: YES];
[smartViewVC.view setHidden: NO];
......
......@@ -157,7 +157,6 @@ NSInteger const REQUEST_SEG = 1;
-(void) reloadData
{
NSLog(@"reload");
[smartView deselectAll:nil];
if (convModel_ == nil)
return;
......@@ -207,7 +206,6 @@ NSInteger const REQUEST_SEG = 1;
auto it = getConversationFromUid(std::string([uid UTF8String]), *convModel_);
if (it != convModel_->allFilteredConversations().end()) {
NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:(it - convModel_->allFilteredConversations().begin())];
NSLog(@"reloadConversationWithUid: %@", uid);
[smartView reloadDataForRowIndexes:indexSet
columnIndexes:[NSIndexSet indexSetWithIndex:0]];
}
......
......@@ -61,6 +61,7 @@ NSString* const TIME_BOX_HEIGHT = @"34";
[self.msgView setEditable:NO];
acceptButton.image = [NSColor image: [NSImage imageNamed:@"ic_file_upload.png"] tintedWithColor:[NSColor greenSuccessColor]];
declineButton.image = [NSColor image: [NSImage imageNamed:@"ic_action_cancel.png"] tintedWithColor:[NSColor redColor]];
msgView.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
}
- (void) setupForInteraction:(uint64_t)inter isFailed:(bool) failed {
......@@ -110,6 +111,8 @@ NSString* const TIME_BOX_HEIGHT = @"34";
NSArray* constraints = [[[constraintsMessageHorizontal arrayByAddingObjectsFromArray:constraintsMessageVertical]
arrayByAddingObject:centerMessageConstraint] arrayByAddingObjectsFromArray:constraintsVerticalTimeBox];
[NSLayoutConstraint activateConstraints:constraints];
//update message frame immediatly
[self.msgView setNeedsDisplay:YES];
}
- (void) updateImageConstraint: (CGFloat) width andHeight: (CGFloat) height {
......
......@@ -82,6 +82,11 @@
*/
@property (nonatomic, strong) NSColor* cornerColor;
/*
* Font size of the button title.
*/
@property CGFloat fontSize;
@end
......@@ -212,6 +212,14 @@
style, NSParagraphStyleAttributeName,
[NSColor whiteColor],
NSForegroundColorAttributeName, nil];
if(self.fontSize) {
NSFont *font = [NSFont fontWithName:@"Helvetica Neue Light" size: self.fontSize];
att = [[NSDictionary alloc] initWithObjectsAndKeys:
font,NSFontAttributeName,
style, NSParagraphStyleAttributeName,
[NSColor whiteColor], NSForegroundColorAttributeName, nil];
}
rect.size = [[self title] sizeWithAttributes:att];
rect.origin.x = floor( NSMidX([self bounds]) - rect.size.width / 2 );
......
This diff is collapsed.
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14313.18"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
......@@ -119,6 +119,7 @@
<connections>
<outlet property="acceptButton" destination="829-to-lM4" id="rZu-K5-leg"/>
<outlet property="declineButton" destination="rGw-yF-hxm" id="cqg-aF-bBK"/>
<outlet property="msgBackground" destination="9bA-XZ-y8X" id="HSQ-eX-LaA"/>
<outlet property="photoView" destination="bbY-3f-2Wi" id="jZb-ne-NZj"/>
<outlet property="timeBox" destination="Ko5-2g-foK" id="wNa-aC-Y5o"/>
<outlet property="timeLabel" destination="EyK-S9-kZz" id="slc-h9-LP4"/>
......@@ -367,6 +368,7 @@
</constraints>
<connections>
<outlet property="declineButton" destination="3gN-Us-HWE" id="WYo-gh-5fw"/>
<outlet property="msgBackground" destination="YEa-1b-wNo" id="vVZ-CE-r2Q"/>
<outlet property="photoView" destination="ocf-Pp-J4F" id="e04-18-iXb"/>
<outlet property="progressIndicator" destination="7hN-OZ-vA4" id="Wda-hA-Ne0"/>
<outlet property="timeBox" destination="dDX-sZ-ylI" id="rAa-Jo-eXW"/>
......@@ -476,6 +478,7 @@
</constraints>
<connections>
<outlet property="declineButton" destination="2cr-cl-DfB" id="0Rk-eA-Fyg"/>
<outlet property="msgBackground" destination="UY2-T5-a0P" id="mFn-YC-kPu"/>
<outlet property="progressIndicator" destination="Sv9-jf-3cY" id="6Y2-mi-Ml4"/>
<outlet property="timeBox" destination="x2B-gu-YBe" id="xPh-3D-3a0"/>
<outlet property="timeLabel" destination="2IY-Tt-uAF" id="uee-Wx-1bT"/>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment