blob: 6b1788ffe2b8bf2a20616553c48f87b4fc965375 [file] [log] [blame]
Andreas Traczyk252a94a2018-04-20 16:36:20 -04001
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04002/*
Sébastien Blin029ffa82019-01-02 17:43:48 -05003 * Copyright (C) 2015-2019 Savoir-faire Linux Inc.
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04004 * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
Anthony Léonard2382b562017-12-13 15:51:28 -05005 * Anthony Léonard <anthony.leonard@savoirfairelinux.com>
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04006 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040022#import <QPixmap>
23#import <QtMacExtras/qmacfunctions.h>
24
Anthony Léonard2382b562017-12-13 15:51:28 -050025// LRC
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040026#import <globalinstances.h>
Anthony Léonard2382b562017-12-13 15:51:28 -050027#import <api/interaction.h>
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040028
29#import "MessagesVC.h"
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040030#import "views/IMTableCellView.h"
31#import "views/MessageBubbleView.h"
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040032#import "views/NSImage+Extensions.h"
Anthony Léonard2382b562017-12-13 15:51:28 -050033#import "delegates/ImageManipulationDelegate.h"
Anthony Léonard6f819752018-01-05 09:53:40 -050034#import "utils.h"
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040035#import "views/NSColor+RingTheme.h"
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040036#import "views/IconButton.h"
37#import <QuickLook/QuickLook.h>
38#import <Quartz/Quartz.h>
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -040039#import <AVFoundation/AVFoundation.h>
40
41#import "RecordFileVC.h"
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040042
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040043
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040044@interface MessagesVC () <NSTableViewDelegate, NSTableViewDataSource, QLPreviewPanelDataSource> {
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040045
Anthony Léonard2382b562017-12-13 15:51:28 -050046 __unsafe_unretained IBOutlet NSTableView* conversationView;
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040047 __unsafe_unretained IBOutlet NSView* containerView;
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -050048 __unsafe_unretained IBOutlet NSTextField* messageField;
49 __unsafe_unretained IBOutlet IconButton *sendFileButton;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -040050 __unsafe_unretained IBOutlet IconButton *recordVideoButton;
51 __unsafe_unretained IBOutlet IconButton *recordAudioButton;
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -050052 __unsafe_unretained IBOutlet NSLayoutConstraint* sendPanelHeight;
53 __unsafe_unretained IBOutlet NSLayoutConstraint* messagesBottomMargin;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -040054 IBOutlet NSPopover *recordMessagePopover;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040055
Kateryna Kostiukc867eb92020-03-08 13:15:17 -040056 QString convUid_;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050057 lrc::api::ConversationModel* convModel_;
Anthony Léonard2382b562017-12-13 15:51:28 -050058 const lrc::api::conversation::Info* cachedConv_;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -040059 lrc::api::AVModel* avModel;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050060 QMetaObject::Connection newInteractionSignal_;
Anthony Léonard2382b562017-12-13 15:51:28 -050061
62 // Both are needed to invalidate cached conversation as pointer
63 // may not be referencing the same conversation anymore
64 QMetaObject::Connection modelSortedSignal_;
65 QMetaObject::Connection filterChangedSignal_;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050066 QMetaObject::Connection interactionStatusUpdatedSignal_;
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -040067 QMetaObject::Connection peerComposingMsgSignal_;
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040068 NSString* previewImage;
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -050069 NSMutableDictionary *pendingMessagesToSend;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -040070 RecordFileVC * recordingController;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040071}
72
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040073@end
74
Anthony Léonardf2bb17d2018-02-15 17:18:09 -050075// Tags for view
76NSInteger const GENERIC_INT_TEXT_TAG = 100;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040077NSInteger const GENERIC_INT_TIME_TAG = 200;
78
79// views size
80CGFloat const GENERIC_CELL_HEIGHT = 60;
81CGFloat const TIME_BOX_HEIGHT = 34;
82CGFloat const MESSAGE_TEXT_PADDING = 10;
83CGFloat const MAX_TRANSFERED_IMAGE_SIZE = 250;
84CGFloat const BUBBLE_HEIGHT_FOR_TRANSFERED_FILE = 87;
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -040085CGFloat const HEIGHT_FOR_COMPOSING_INDICATOR = 37;
Kateryna Kostiuka1201922020-04-20 11:59:35 -040086CGFloat const HEIGHT_DEFAULT = 34;
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -050087NSInteger const MEESAGE_MARGIN = 21;
88NSInteger const SEND_PANEL_DEFAULT_HEIGHT = 60;
89NSInteger const SEND_PANEL_MAX_HEIGHT = 120;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -050090
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -040091BOOL peerComposingMessage = false;
92BOOL composingMessage = false;
93
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040094@implementation MessagesVC
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040095
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040096
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040097//MessageBuble type
98typedef NS_ENUM(NSInteger, MessageSequencing) {
99 SINGLE_WITH_TIME = 0,
100 SINGLE_WITHOUT_TIME = 1,
101 FIRST_WITH_TIME = 2,
102 FIRST_WITHOUT_TIME = 3,
103 MIDDLE_IN_SEQUENCE = 5,
104 LAST_IN_SEQUENCE = 6,
105};
106
107- (void)awakeFromNib
108{
109 NSNib *cellNib = [[NSNib alloc] initWithNibNamed:@"MessageCells" bundle:nil];
110 [conversationView registerNib:cellNib forIdentifier:@"LeftIncomingFileView"];
111 [conversationView registerNib:cellNib forIdentifier:@"LeftOngoingFileView"];
112 [conversationView registerNib:cellNib forIdentifier:@"LeftFinishedFileView"];
113 [conversationView registerNib:cellNib forIdentifier:@"RightOngoingFileView"];
114 [conversationView registerNib:cellNib forIdentifier:@"RightFinishedFileView"];
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400115 [conversationView registerNib:cellNib forIdentifier:@"PeerComposingMsgView"];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500116 [[conversationView.enclosingScrollView contentView] setCopiesOnScroll:NO];
117 [messageField setFocusRingType:NSFocusRingTypeNone];
118 [conversationView setWantsLayer:YES];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400119}
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500120
121- (instancetype)initWithCoder:(NSCoder *)coder
122{
123 self = [super initWithCoder:coder];
124 if (self) {
125 pendingMessagesToSend = [[NSMutableDictionary alloc] init];
126 }
127 return self;
128}
129
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400130-(void) setAVModel: (lrc::api::AVModel*) avmodel {
131 avModel = avmodel;
132 if (recordingController == nil) {
133 recordingController = [[RecordFileVC alloc] initWithNibName:@"RecordFileVC" bundle:nil avModel: self->avModel];
134 recordingController.delegate = self;
135 }
136}
137
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500138- (void)setMessage:(NSString *)newValue {
139 _message = [newValue removeEmptyLinesAtBorders];
140}
141
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400142-(void) clearData {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400143 if (!convUid_.isEmpty()) {
144 pendingMessagesToSend[convUid_.toNSString()] = messageField.stringValue;
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500145 }
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400146 cachedConv_ = nil;
147 convUid_ = "";
148 convModel_ = nil;
149
150 QObject::disconnect(modelSortedSignal_);
151 QObject::disconnect(filterChangedSignal_);
152 QObject::disconnect(interactionStatusUpdatedSignal_);
153 QObject::disconnect(newInteractionSignal_);
Kateryna Kostiuk5acaefd2020-03-25 11:14:25 -0400154 QObject::disconnect(peerComposingMsgSignal_);
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400155 [self closeRecordingView];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400156}
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400157
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500158-(void) scrollToBottom {
159 CGRect visibleRect = [conversationView enclosingScrollView].contentView.visibleRect;
160 NSRange range = [conversationView rowsInRect:visibleRect];
161 NSIndexSet* visibleIndexes = [NSIndexSet indexSetWithIndexesInRange:range];
162 NSUInteger lastvisibleRow = [visibleIndexes lastIndex];
163 if (([conversationView numberOfRows] > 0) &&
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400164 lastvisibleRow > ([conversationView numberOfRows] - 3)) {
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500165 [conversationView scrollToEndOfDocument:nil];
166 }
167}
168
Anthony Léonard2382b562017-12-13 15:51:28 -0500169-(const lrc::api::conversation::Info*) getCurrentConversation
170{
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400171 if (convModel_ == nil || convUid_.isEmpty())
Anthony Léonard2382b562017-12-13 15:51:28 -0500172 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400173
Anthony Léonard2382b562017-12-13 15:51:28 -0500174 if (cachedConv_ != nil)
175 return cachedConv_;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400176
Anthony Léonard6f819752018-01-05 09:53:40 -0500177 auto it = getConversationFromUid(convUid_, *convModel_);
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400178 if (it != convModel_->allFilteredConversations().end())
Anthony Léonard2382b562017-12-13 15:51:28 -0500179 cachedConv_ = &(*it);
180
181 return cachedConv_;
182}
183
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400184-(void) reloadConversationForMessage:(uint64_t) uid shouldUpdateHeight:(bool)update {
185 auto* conv = [self getCurrentConversation];
186
187 if (conv == nil)
188 return;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400189 auto it = conv->interactions.find(uid);
190 if (it == conv->interactions.end()) {
191 return;
192 }
193 auto itIndex = distance(conv->interactions.begin(),it);
Kateryna Kostiuka1201922020-04-20 11:59:35 -0400194 if (itIndex >= ([conversationView numberOfRows] - 1)) {
195 return;
196 }
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400197 NSRange rangeToUpdate = NSMakeRange(itIndex, 2);
198 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndexesInRange:rangeToUpdate];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400199 //reload previous message to update bubbleview
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400200 if (itIndex > 0) {
201 auto previousIt = it;
202 previousIt--;
203 auto previousInteraction = previousIt->second;
204 if (previousInteraction.type == lrc::api::interaction::Type::TEXT) {
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400205 NSRange range = NSMakeRange(itIndex - 1, 3);
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400206 indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
207 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400208 }
209 if (update) {
210 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
211 }
212 [conversationView reloadDataForRowIndexes: indexSet
213 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400214 CGRect visibleRect = [conversationView enclosingScrollView].contentView.visibleRect;
215 NSRange range = [conversationView rowsInRect:visibleRect];
216 NSIndexSet* visibleIndexes = [NSIndexSet indexSetWithIndexesInRange:range];
217 NSUInteger lastvisibleRow = [visibleIndexes lastIndex];
218 if (([conversationView numberOfRows] > 0) &&
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400219 lastvisibleRow > ([conversationView numberOfRows] - 3)) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400220 [conversationView scrollToEndOfDocument:nil];
221 }
222}
223
224-(void) reloadConversationForMessage:(uint64_t) uid shouldUpdateHeight:(bool)update updateConversation:(bool) updateConversation {
225 auto* conv = [self getCurrentConversation];
226
227 if (conv == nil)
228 return;
229 auto it = distance(conv->interactions.begin(),conv->interactions.find(uid));
230 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:it];
231 //reload previous message to update bubbleview
232 if (it > 0) {
233 NSRange range = NSMakeRange(it - 1, it);
234 indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
235 }
236 if (update) {
237 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
238 }
239 [conversationView reloadDataForRowIndexes: indexSet
240 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
241 if (update) {
242 [conversationView scrollToEndOfDocument:nil];
243 }
244}
245
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400246-(void)setConversationUid:(const QString&)convUid model:(lrc::api::ConversationModel *)model
Anthony Léonard2382b562017-12-13 15:51:28 -0500247{
248 if (convUid_ == convUid && convModel_ == model)
249 return;
250
251 cachedConv_ = nil;
252 convUid_ = convUid;
253 convModel_ = model;
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400254 peerComposingMessage = false;
255 composingMessage = false;
Anthony Léonard2382b562017-12-13 15:51:28 -0500256
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500257 // Signal triggered when messages are received or their status updated
258 QObject::disconnect(newInteractionSignal_);
259 QObject::disconnect(interactionStatusUpdatedSignal_);
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400260 QObject::disconnect(peerComposingMsgSignal_);
261
262 peerComposingMsgSignal_ = QObject::connect(convModel_,
263 &lrc::api::ConversationModel::composingStatusChanged,
264 [self](const QString &uid,
265 const QString &contactUri,
266 bool isComposing) {
267 if (uid != convUid_)
268 return;
269 bool shouldUpdate = isComposing != peerComposingMessage;
270 if (!shouldUpdate) {
271 return;
272 }
273 // reload and update height for composing indicator
274 peerComposingMessage = isComposing;
275 auto* conv = [self getCurrentConversation];
276 if (conv == nil)
277 return;
278 auto row = [conversationView numberOfRows] - 1;
279 if (row < 0) {
280 return;
281 }
282 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:row];
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400283 if(peerComposingMessage) {
284 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
285 } else {
286 //whait for possible incoming message to avoid view jumping
287 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
288 if (!peerComposingMessage) {
289 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
290 }
291 });
292 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400293 [conversationView reloadDataForRowIndexes: indexSet
294 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400295 CGRect visibleRect = [conversationView enclosingScrollView].contentView.visibleRect;
296 NSRange range = [conversationView rowsInRect:visibleRect];
297 NSIndexSet* visibleIndexes = [NSIndexSet indexSetWithIndexesInRange:range];
298 NSUInteger lastvisibleRow = [visibleIndexes lastIndex];
299 if (([conversationView numberOfRows] > 0) &&
300 lastvisibleRow > ([conversationView numberOfRows] - 3)) {
301 [conversationView scrollToEndOfDocument:nil];
302 }
303 });
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500304 newInteractionSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::newInteraction,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400305 [self](const QString& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400306 if (uid != convUid_)
307 return;
308 cachedConv_ = nil;
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400309 peerComposingMessage = false;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400310 [conversationView noteNumberOfRowsChanged];
311 [self reloadConversationForMessage:interactionId shouldUpdateHeight:YES];
312 });
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500313 interactionStatusUpdatedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::interactionStatusUpdated,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400314 [self](const QString& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500315 if (uid != convUid_)
316 return;
317 cachedConv_ = nil;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400318 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
319 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
320 convModel_->refreshFilter();
321 }
322 [self reloadConversationForMessage:interactionId shouldUpdateHeight:YES];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500323 });
Anthony Léonard2382b562017-12-13 15:51:28 -0500324
325 // Signals tracking changes in conversation list, we need them as cached conversation can be invalid
326 // after a reordering.
327 QObject::disconnect(modelSortedSignal_);
328 QObject::disconnect(filterChangedSignal_);
329 modelSortedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelSorted,
330 [self](){
331 cachedConv_ = nil;
332 });
333 filterChangedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::filterChanged,
334 [self](){
335 cachedConv_ = nil;
336 });
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400337 if (pendingMessagesToSend[convUid_.toNSString()]) {
338 self.message = pendingMessagesToSend[convUid_.toNSString()];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500339 [self updateSendMessageHeight];
340 } else {
341 self.message = @"";
342 if(messagesBottomMargin.constant != SEND_PANEL_DEFAULT_HEIGHT) {
343 sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
344 messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
345 [self scrollToBottom];
346 }
347 }
348 conversationView.alphaValue = 0.0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500349 [conversationView reloadData];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400350 [conversationView scrollToEndOfDocument:nil];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500351 CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"];
352 fadeIn.fromValue = [NSNumber numberWithFloat:0.0];
353 fadeIn.toValue = [NSNumber numberWithFloat:1.0];
354 fadeIn.duration = 0.4f;
355
356 [conversationView.layer addAnimation:fadeIn forKey:fadeIn.keyPath];
357 conversationView.alphaValue = 1;
358 auto* conv = [self getCurrentConversation];
359
360 if (conv == nil)
361 return;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400362 try {
363 [sendFileButton setEnabled:(convModel_->owner.contactModel->getContact(conv->participants[0]).profileInfo.type != lrc::api::profile::Type::SIP)];
364 } catch (std::out_of_range& e) {
365 NSLog(@"contact out of range");
366 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400367}
368
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400369#pragma mark - configure cells
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400370
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400371-(NSTableCellView*) makeGenericInteractionViewForTableView:(NSTableView*)tableView withText:(NSString*)text andTime:(NSString*) time
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500372{
373 NSTableCellView* result = [tableView makeViewWithIdentifier:@"GenericInteractionView" owner:self];
374 NSTextField* textField = [result viewWithTag:GENERIC_INT_TEXT_TAG];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400375 NSTextField* timeField = [result viewWithTag:GENERIC_INT_TIME_TAG];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500376
377 // TODO: Fix symbol in LRC
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500378 NSString* fixedString = [[text stringByReplacingOccurrencesOfString:@"🕽" withString:@""] stringByReplacingOccurrencesOfString:@"📞" withString:@""];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500379 [textField setStringValue:fixedString];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400380 [timeField setStringValue:time];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500381
382 return result;
383}
384
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400385-(NSTableCellView*) configureViewforTransfer:(lrc::api::interaction::Info)interaction interactionID: (uint64_t) interactionID tableView:(NSTableView*)tableView
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500386{
387 IMTableCellView* result;
388
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400389 auto type = interaction.type;
390 auto status = interaction.status;
391
392 NSString* fileName = @"incoming file";
393
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500394 // First, view is created
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400395 if (!interaction.authorUri.isEmpty()) {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500396 switch (status) {
397 case lrc::api::interaction::Status::TRANSFER_CREATED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400398 case lrc::api::interaction::Status::TRANSFER_AWAITING_HOST: {
399 result = [tableView makeViewWithIdentifier:@"LeftIncomingFileView" owner: conversationView];
400 [result.acceptButton setAction:@selector(acceptIncomingFile:)];
401 [result.acceptButton setTarget:self];
402 [result.declineButton setAction:@selector(declineIncomingFile:)];
403 [result.declineButton setTarget:self];
404 break;}
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500405 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400406 case lrc::api::interaction::Status::TRANSFER_ONGOING: {
407 result = [tableView makeViewWithIdentifier:@"LeftOngoingFileView" owner:conversationView];
408 [result.progressIndicator startAnimation:conversationView];
409 [result.declineButton setAction:@selector(declineIncomingFile:)];
410 [result.declineButton setTarget:self];
411 break;}
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500412 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400413 result = [tableView makeViewWithIdentifier:@"LeftFinishedFileView" owner:conversationView];
414 [result.transferedFileName setAction:@selector(imagePreview:)];
415 [result.transferedFileName setTarget:self];
416 [result.transferedFileName.cell setHighlightsBy:NSContentsCellMask];
417 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500418 case lrc::api::interaction::Status::TRANSFER_CANCELED:
419 case lrc::api::interaction::Status::TRANSFER_ERROR:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400420 result = [tableView makeViewWithIdentifier:@"LeftFinishedFileView" owner:conversationView];
421 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500422 }
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400423 } else {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400424 NSString* fileName = @"sent file";
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500425 switch (status) {
426 case lrc::api::interaction::Status::TRANSFER_CREATED:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500427 case lrc::api::interaction::Status::TRANSFER_ONGOING:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400428 case lrc::api::interaction::Status::TRANSFER_AWAITING_PEER:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500429 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400430 result = [tableView makeViewWithIdentifier:@"RightOngoingFileView" owner:conversationView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500431 [result.progressIndicator startAnimation:nil];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400432 [result.declineButton setAction:@selector(declineIncomingFile:)];
433 [result.declineButton setTarget:self];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500434 break;
435 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400436 result = [tableView makeViewWithIdentifier:@"RightFinishedFileView" owner:conversationView];
437 [result.transferedFileName setAction:@selector(imagePreview:)];
438 [result.transferedFileName setTarget:self];
439 [result.transferedFileName.cell setHighlightsBy:NSContentsCellMask];
440 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500441 case lrc::api::interaction::Status::TRANSFER_CANCELED:
442 case lrc::api::interaction::Status::TRANSFER_ERROR:
Olivier Soldanoe521a182018-02-26 16:55:19 -0500443 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400444 result = [tableView makeViewWithIdentifier:@"RightFinishedFileView" owner:conversationView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500445 }
446 }
447
448 // Then status label is updated if needed
449 switch (status) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400450 [result.statusLabel setTextColor:[NSColor textColor]];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500451 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400452 [result.statusLabel setTextColor:[NSColor greenSuccessColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500453 [result.statusLabel setStringValue:NSLocalizedString(@"Success", @"File transfer successful label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500454 break;
455 case lrc::api::interaction::Status::TRANSFER_CANCELED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400456 [result.statusLabel setTextColor:[NSColor orangeColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500457 [result.statusLabel setStringValue:NSLocalizedString(@"Canceled", @"File transfer canceled label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500458 break;
459 case lrc::api::interaction::Status::TRANSFER_ERROR:
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400460 [result.statusLabel setTextColor:[NSColor errorTransferColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500461 [result.statusLabel setStringValue:NSLocalizedString(@"Failed", @"File transfer failed label")];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500462 break;
463 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400464 [result.statusLabel setTextColor:[NSColor textColor]];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500465 [result.statusLabel setStringValue:NSLocalizedString(@"Unjoinable", @"File transfer peer unjoinable label")];
466 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500467 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400468 result.transferedImage.image = nil;
Kateryna Kostiukeaf1bc82018-10-12 14:33:50 -0400469 [result.openImagebutton setHidden:YES];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400470 [result.msgBackground setHidden:NO];
471 [result invalidateImageConstraints];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400472 NSString* name = interaction.body.toNSString();
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400473 if (name.length > 0) {
Kateryna Kostiuk67735232018-05-10 15:05:32 -0400474 fileName = [name lastPathComponent];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400475 }
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400476 NSFont *nameFont = [NSFont userFontOfSize:14.0];
477 NSColor *nameColor = [NSColor textColor];
478 NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
479 paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
480 paragraphStyle.alignment = NSTextAlignmentLeft;
481 NSDictionary *nameAttr = [NSDictionary dictionaryWithObjectsAndKeys:nameFont,NSFontAttributeName,
482 nameColor,NSForegroundColorAttributeName,
483 paragraphStyle,NSParagraphStyleAttributeName, nil];
484 NSAttributedString* nameAttributedString = [[NSAttributedString alloc] initWithString:fileName attributes:nameAttr];
485 result.transferedFileName.attributedTitle = nameAttributedString;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400486 if (status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400487 NSColor *higlightColor = [NSColor grayColor];
488 NSDictionary *alternativeNametAttr = [NSDictionary dictionaryWithObjectsAndKeys:nameFont,NSFontAttributeName,
489 higlightColor,NSForegroundColorAttributeName,
490 paragraphStyle,NSParagraphStyleAttributeName, nil];
491 NSAttributedString* alternativeString = [[NSAttributedString alloc] initWithString:fileName attributes:alternativeNametAttr];
492 result.transferedFileName.attributedAlternateTitle = alternativeString;
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400493 NSImage* image = [self getImageForFilePath:name];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400494 if (([name rangeOfString:@"/"].location == NSNotFound)) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400495 image = [self getImageForFilePath:[self getDataTransferPath:interactionID]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400496 }
497 if(image != nil) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400498 result.transferedImage.image = image;
499 [result updateImageConstraintWithMax: MAX_TRANSFERED_IMAGE_SIZE];
500 [result.openImagebutton setAction:@selector(imagePreview:)];
501 [result.openImagebutton setTarget:self];
Kateryna Kostiukeaf1bc82018-10-12 14:33:50 -0400502 [result.openImagebutton setHidden:NO];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400503 }
504 }
505 [result setupForInteraction:interactionID];
506 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
507 NSString* timeString = [self timeForMessage: msgTime];
508 result.timeLabel.stringValue = timeString;
509 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
510 if (!isOutgoing) {
511 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
512 auto* conv = [self getCurrentConversation];
513 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
514 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500515 return result;
516}
517
Anthony Léonard2382b562017-12-13 15:51:28 -0500518#pragma mark - NSTableViewDelegate methods
519- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400520{
521 return YES;
522}
523
Anthony Léonard2382b562017-12-13 15:51:28 -0500524- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400525{
Anthony Léonard2382b562017-12-13 15:51:28 -0500526 return NO;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400527}
528
Anthony Léonard2382b562017-12-13 15:51:28 -0500529- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400530{
Anthony Léonard2382b562017-12-13 15:51:28 -0500531 auto* conv = [self getCurrentConversation];
Anthony Léonard2382b562017-12-13 15:51:28 -0500532 if (conv == nil)
533 return nil;
534
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400535 IMTableCellView* result;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500536 auto it = conv->interactions.begin();
Kateryna Kostiuka1201922020-04-20 11:59:35 -0400537 auto size = [conversationView numberOfRows] - 1;
Kateryna Kostiuk0c068552020-03-30 09:48:17 -0400538 if (row > size) {
539 return [[NSView alloc] init];
540 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400541
542 if (row == size) {
Kateryna Kostiukb39ca192020-03-27 14:01:00 -0400543 if (size < 1) {
544 return [[NSView alloc] init];
545 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400546 //last row peer composing view
547 result = [tableView makeViewWithIdentifier:@"PeerComposingMsgView" owner:conversationView];
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400548 std::advance(it, row-1);
Kateryna Kostiuk0c068552020-03-30 09:48:17 -0400549 if (it != conv->interactions.end()) {
550 //if previous message not from peer display avatar
551 auto interaction = it->second;
552 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
553 [result.photoView setHidden: YES];
554 if(isOutgoing) {
555 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
556 auto* conv = [self getCurrentConversation];
557 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
558 [result.photoView setHidden: NO];
559 }
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400560 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400561 CGFloat alpha = peerComposingMessage ? 1 : 0;
562 result.alphaValue = 0;
563 [result animateCompozingIndicator: NO];
564 if (alpha == 1) {
565 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
566 if (peerComposingMessage) {
567 result.alphaValue = alpha;
568 [result animateCompozingIndicator: YES];
569 }
570 });
571 }
572 return result;
573 }
Anthony Léonard2382b562017-12-13 15:51:28 -0500574
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500575 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500576
Kateryna Kostiuk0c068552020-03-30 09:48:17 -0400577 if (it == conv->interactions.end()) {
578 return [[NSView alloc] init];
579 }
580
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400581 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500582 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
583
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500584 switch (interaction.type) {
585 case lrc::api::interaction::Type::TEXT:
586 if (isOutgoing) {
587 result = [tableView makeViewWithIdentifier:@"RightMessageView" owner:self];
588 } else {
589 result = [tableView makeViewWithIdentifier:@"LeftMessageView" owner:self];
590 }
591 break;
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400592 case lrc::api::interaction::Type::DATA_TRANSFER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400593 return [self configureViewforTransfer:interaction interactionID: it->first tableView:tableView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500594 break;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500595 case lrc::api::interaction::Type::CONTACT:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400596 case lrc::api::interaction::Type::CALL: {
597 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
598 NSString* timeString = [self timeForMessage: msgTime];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400599 return [self makeGenericInteractionViewForTableView:tableView withText:interaction.body.toNSString() andTime:timeString];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400600 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500601 default: // If interaction is not of a known type
602 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400603 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400604 MessageSequencing sequence = [self computeSequencingFor:row];
605 BubbleType type = SINGLE;
606 if (sequence == FIRST_WITHOUT_TIME || sequence == FIRST_WITH_TIME) {
607 type = FIRST;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400608 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400609 if (sequence == MIDDLE_IN_SEQUENCE) {
610 type = MIDDLE;
611 }
612 if (sequence == LAST_IN_SEQUENCE) {
613 type = LAST;
614 }
615 result.msgBackground.type = type;
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400616 bool sendingFail = false;
617 [result.messageStatus setHidden:YES];
618 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
619 if (interaction.status == lrc::api::interaction::Status::SENDING) {
620 [result.messageStatus setHidden:NO];
621 [result.sendingMessageIndicator startAnimation:nil];
622 [result.messageFailed setHidden:YES];
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400623 } else if (interaction.status == lrc::api::interaction::Status::FAILURE) {
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400624 [result.messageStatus setHidden:NO];
625 [result.sendingMessageIndicator setHidden:YES];
626 [result.messageFailed setHidden:NO];
627 sendingFail = true;
628 }
629 }
630 [result setupForInteraction:it->first isFailed: sendingFail];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400631 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400632 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400633 [result.msgBackground setNeedsDisplay:YES];
634 [result setNeedsDisplay:YES];
635 [result.timeBox setNeedsDisplay:YES];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400636
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400637 NSString *text = interaction.body.toNSString();
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400638 text = [text removeEmptyLinesAtBorders];
639
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400640 NSMutableAttributedString* msgAttString =
Kateryna Kostiuk5a41a8d2020-05-13 09:05:56 -0400641 [[NSMutableAttributedString alloc] initWithString:text
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400642 attributes:[self messageAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400643
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400644 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400645
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400646 [result updateMessageConstraint:messageSize.width andHeight:messageSize.height timeIsVisible:shouldDisplayTime isTopPadding: shouldApplyPadding];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400647 [[result.msgView textStorage] appendAttributedString:msgAttString];
Anthony Léonard2382b562017-12-13 15:51:28 -0500648
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400649 NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
650 NSArray *matches = [linkDetector matchesInString:result.msgView.string options:0 range:NSMakeRange(0, result.msgView.string.length)];
651
652 [result.msgView.textStorage beginEditing];
653
654 for (NSTextCheckingResult *match in matches) {
655 if (!match.URL) continue;
656
657 NSDictionary *linkAttributes = @{
658 NSLinkAttributeName: match.URL,
659 };
660 [result.msgView.textStorage addAttributes:linkAttributes range:match.range];
661 }
662
663 [result.msgView.textStorage endEditing];
664
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400665 if (shouldDisplayTime) {
666 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
667 NSString* timeString = [self timeForMessage: msgTime];
668 result.timeLabel.stringValue = timeString;
Anthony Léonard64e19672018-01-18 16:40:34 -0500669 }
670
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400671 bool shouldDisplayAvatar = (sequence != MIDDLE_IN_SEQUENCE && sequence != FIRST_WITHOUT_TIME
672 && sequence != FIRST_WITH_TIME) ? YES : NO;
673 [result.photoView setHidden:!shouldDisplayAvatar];
674 if (!isOutgoing && shouldDisplayAvatar) {
675 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
676 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
677 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400678 return result;
679}
680
Anthony Léonard2382b562017-12-13 15:51:28 -0500681- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400682{
Anthony Léonard2382b562017-12-13 15:51:28 -0500683 double someWidth = tableView.frame.size.width * 0.7;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400684
Anthony Léonard2382b562017-12-13 15:51:28 -0500685 auto* conv = [self getCurrentConversation];
686
687 if (conv == nil)
Kateryna Kostiuk0c068552020-03-30 09:48:17 -0400688 return HEIGHT_DEFAULT;
689
Kateryna Kostiuka1201922020-04-20 11:59:35 -0400690 auto size = [conversationView numberOfRows] - 1;
691 if (row >= size) {
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400692 //last item peer composing view
693 if (peerComposingMessage) {
694 return HEIGHT_FOR_COMPOSING_INDICATOR;
695 }
Kateryna Kostiuka1201922020-04-20 11:59:35 -0400696 return 1;
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400697 }
698
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500699 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500700
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500701 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500702
Kateryna Kostiuk0c068552020-03-30 09:48:17 -0400703 if (it == conv->interactions.end()) {
704 return HEIGHT_DEFAULT;
705 }
706
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400707 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500708
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400709 MessageSequencing sequence = [self computeSequencingFor:row];
710
711 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
712
713
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400714 if(interaction.type == lrc::api::interaction::Type::DATA_TRANSFER) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400715
716 if( interaction.status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400717 NSString* name = interaction.body.toNSString();
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400718 NSImage* image = [self getImageForFilePath:name];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400719 if (([name rangeOfString:@"/"].location == NSNotFound)) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400720 image = [self getImageForFilePath:[self getDataTransferPath:it->first]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400721 }
722 if (image != nil) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400723 CGFloat widthScaleFactor = MAX_TRANSFERED_IMAGE_SIZE / image.size.width;
724 CGFloat heightScaleFactor = MAX_TRANSFERED_IMAGE_SIZE / image.size.height;
725 CGFloat heigt = 0;
726 if((widthScaleFactor >= 1) && (heightScaleFactor >= 1)) {
727 heigt = image.size.height;
728 } else {
729 CGFloat scale = MIN(widthScaleFactor, heightScaleFactor);
730 heigt = image.size.height * scale;
731 }
732 return heigt + TIME_BOX_HEIGHT;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400733 }
734 }
735 return BUBBLE_HEIGHT_FOR_TRANSFERED_FILE + TIME_BOX_HEIGHT;
736 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500737
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500738 if(interaction.type == lrc::api::interaction::Type::CONTACT || interaction.type == lrc::api::interaction::Type::CALL)
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400739 return GENERIC_CELL_HEIGHT;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500740
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400741 NSString *text = interaction.body.toNSString();
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400742 text = [text removeEmptyLinesAtBorders];
743
744 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400745 CGFloat singleLignMessageHeight = 15;
746
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400747 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
748
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400749 if (shouldDisplayTime) {
750 return MAX(messageSize.height + TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2,
751 TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2 + singleLignMessageHeight);
752 }
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400753 if(shouldApplyPadding) {
754 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2 + 15,
755 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2 + 15);
756 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400757 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2,
758 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2);
759}
760
761#pragma mark - message view parameters
762
763-(NSString *) getDataTransferPath:(uint64_t)interactionId {
764 lrc::api::datatransfer::Info info = {};
765 convModel_->getTransferInfo(interactionId, info);
766 double convertData = static_cast<double>(info.totalSize);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400767 return info.path.toNSString();
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400768}
769
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400770-(NSImage*) getImageForFilePath: (NSString *) path {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400771 if (path.length <= 0) {return nil;}
772 if (![[NSFileManager defaultManager] fileExistsAtPath: path]) {return nil;}
773 NSImage* transferedImage = [[NSImage alloc] initWithContentsOfFile: path];
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400774 return transferedImage;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400775}
776
777-(CGSize) sizeFor:(NSString *) message maxWidth:(CGFloat) width {
778 CGFloat horizaontalMargin = 6;
Anthony Léonard2382b562017-12-13 15:51:28 -0500779 NSMutableAttributedString* msgAttString =
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400780 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@", message]
Anthony Léonard2382b562017-12-13 15:51:28 -0500781 attributes:[self messageAttributes]];
782
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400783 CGFloat finalWidth = MIN(msgAttString.size.width + horizaontalMargin * 2, width);
784 NSRect frame = NSMakeRect(0, 0, finalWidth, msgAttString.size.height);
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400785 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400786 [[tv textStorage] setAttributedString:msgAttString];
787 [tv sizeToFit];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400788 return tv.frame.size;
789}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400790
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400791-(MessageSequencing) computeSequencingFor:(NSInteger) row {
Kateryna Kostiukcd9397f2020-04-13 09:40:53 -0400792 if (row >= conversationView.numberOfRows - 1) {
793 return SINGLE_WITHOUT_TIME;
794 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400795 auto* conv = [self getCurrentConversation];
796 if (conv == nil)
Kateryna Kostiukcd9397f2020-04-13 09:40:53 -0400797 return SINGLE_WITHOUT_TIME;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400798 auto it = conv->interactions.begin();
799 std::advance(it, row);
Kateryna Kostiuk0c068552020-03-30 09:48:17 -0400800 if (it == conv->interactions.end()) {
801 return SINGLE_WITHOUT_TIME;
802 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400803 auto interaction = it->second;
804 if (interaction.type != lrc::api::interaction::Type::TEXT) {
805 return SINGLE_WITH_TIME;
806 }
Kateryna Kostiukcd9397f2020-04-13 09:40:53 -0400807 // first message in comversation
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400808 if (row == 0) {
809 if (it == conv->interactions.end()) {
810 return SINGLE_WITH_TIME;
811 }
812 auto nextIt = it;
813 nextIt++;
Kateryna Kostiukcd9397f2020-04-13 09:40:53 -0400814 if (nextIt == conv->interactions.end()) {
815 return SINGLE_WITH_TIME;
816 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400817 auto nextInteraction = nextIt->second;
818 if ([self sequenceChangedFrom:interaction to: nextInteraction]) {
819 return SINGLE_WITH_TIME;
820 }
821 return FIRST_WITH_TIME;
822 }
Kateryna Kostiukcd9397f2020-04-13 09:40:53 -0400823 // last message in comversation
824 if (row == conversationView.numberOfRows - 2) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400825 if(it == conv->interactions.begin()) {
826 return SINGLE_WITH_TIME;
827 }
828 auto previousIt = it;
829 previousIt--;
830 auto previousInteraction = previousIt->second;
831 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
832 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
833 if (!timeChanged && !authorChanged) {
834 return LAST_IN_SEQUENCE;
835 }
836 if (!timeChanged && authorChanged) {
837 return SINGLE_WITHOUT_TIME;
838 }
839 return SINGLE_WITH_TIME;
840 }
Kateryna Kostiukcd9397f2020-04-13 09:40:53 -0400841 // single message in comversation
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400842 if(it == conv->interactions.begin() || it == conv->interactions.end()) {
843 return SINGLE_WITH_TIME;
844 }
Kateryna Kostiukcd9397f2020-04-13 09:40:53 -0400845 // message in the middle of conversation
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400846 auto previousIt = it;
847 previousIt--;
848 auto previousInteraction = previousIt->second;
849 auto nextIt = it;
850 nextIt++;
Kateryna Kostiukcd9397f2020-04-13 09:40:53 -0400851 if (nextIt == conv->interactions.end()) {
852 return SINGLE_WITHOUT_TIME;
853 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400854 auto nextInteraction = nextIt->second;
855
856 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
857 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
858 bool sequenceWillChange = [self sequenceChangedFrom:interaction to: nextInteraction];
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400859 if (previousInteraction.type == lrc::api::interaction::Type::DATA_TRANSFER) {
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400860 if(!sequenceWillChange) {
861 return FIRST_WITH_TIME;
862 }
863 return SINGLE_WITH_TIME;
864 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400865 if (!sequenceWillChange) {
866 if (!timeChanged && !authorChanged) {
867 return MIDDLE_IN_SEQUENCE;
868 }
869 if (timeChanged) {
870 return FIRST_WITH_TIME;
871 }
872 return FIRST_WITHOUT_TIME;
873 } if (!timeChanged && !authorChanged) {
874 return LAST_IN_SEQUENCE;
875 } if (timeChanged) {
876 return SINGLE_WITH_TIME;
877 }
878 return SINGLE_WITHOUT_TIME;
879}
880
881-(bool) sequenceChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
882 return ([self sequenceTimeChangedFrom:firstInteraction to:secondInteraction] || [self sequenceAuthorChangedFrom:firstInteraction to:secondInteraction]);
883}
884
885-(bool) sequenceTimeChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
886 bool timeChanged = NO;
887 NSDate* firstMessageTime = [NSDate dateWithTimeIntervalSince1970:firstInteraction.timestamp];
888 NSDate* secondMessageTime = [NSDate dateWithTimeIntervalSince1970:secondInteraction.timestamp];
889 bool hourComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitHour];
890 bool minutComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitMinute];
891 if(hourComp != NSOrderedSame || minutComp != NSOrderedSame) {
892 timeChanged = YES;
893 }
894 return timeChanged;
895}
896
897-(bool) sequenceAuthorChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
898 bool authorChanged = YES;
899 bool isOutgoing = lrc::api::interaction::isOutgoing(firstInteraction);
900 if ((secondInteraction.type == lrc::api::interaction::Type::TEXT) && (isOutgoing == lrc::api::interaction::isOutgoing(secondInteraction))) {
901 authorChanged = NO;
902 }
903 return authorChanged;
904}
905
906-(NSString *)timeForMessage:(NSDate*) msgTime {
907 NSDate *today = [NSDate date];
908 NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
Kateryna Kostiukaf6d5e22018-06-12 15:00:00 -0400909 [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:[[NSLocale currentLocale] localeIdentifier]]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400910 if ([[NSCalendar currentCalendar] compareDate:today
911 toDate:msgTime
912 toUnitGranularity:NSCalendarUnitYear]!= NSOrderedSame) {
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500913 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterLongStyle timeStyle:NSDateFormatterMediumStyle];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400914 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400915 if ([[NSCalendar currentCalendar] compareDate:today
916 toDate:msgTime
917 toUnitGranularity:NSCalendarUnitDay]!= NSOrderedSame ||
918 [[NSCalendar currentCalendar] compareDate:today
919 toDate:msgTime
920 toUnitGranularity:NSCalendarUnitMonth]!= NSOrderedSame) {
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500921 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterShortStyle];
922 }
923 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterShortStyle];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400924}
925
Kateryna Kostiukf6317422018-09-27 17:08:20 -0400926- (void) updateSendMessageHeight {
927 NSAttributedString *msgAttString = messageField.attributedStringValue;
928 NSRect frame = NSMakeRect(0, 0, messageField.frame.size.width, msgAttString.size.height);
929 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
930 [[tv textStorage] setAttributedString:msgAttString];
931 [tv sizeToFit];
932 CGFloat height = tv.frame.size.height + MEESAGE_MARGIN * 2;
933 CGFloat newHeight = MIN(SEND_PANEL_MAX_HEIGHT, MAX(SEND_PANEL_DEFAULT_HEIGHT, height));
934 if(messagesBottomMargin.constant == newHeight) {
935 return;
936 }
937 messagesBottomMargin.constant = newHeight;
938 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
939 [self scrollToBottom];
940 sendPanelHeight.constant = newHeight;
941 });
942}
943
Anthony Léonard2382b562017-12-13 15:51:28 -0500944#pragma mark - NSTableViewDataSource
945
946- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
947{
948 auto* conv = [self getCurrentConversation];
949
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400950 // return conversation +1 view for composing indicator
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500951 if (conv)
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400952 return conv->interactions.size() + 1;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500953 else
954 return 0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500955}
956
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400957#pragma mark - Text formatting
958
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400959- (NSMutableDictionary*) messageAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400960{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400961 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400962 attrs[NSForegroundColorAttributeName] = [NSColor labelColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400963 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400964 return attrs;
965}
966
967- (NSParagraphStyle*) paragraphStyle
968{
969 /*
970 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
971 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
972 to copy, we use the default one.
973
974 The default values supplied by the default NSParagraphStyle are:
975 Alignment NSNaturalTextAlignment
976 Tab stops 12 left-aligned tabs, spaced by 28.0 points
977 Line break mode NSLineBreakByWordWrapping
978 All others 0.0
979 */
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400980 NSMutableParagraphStyle* aMutableParagraphStyle =
981 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
982 [aMutableParagraphStyle setHeadIndent:1.0];
983 [aMutableParagraphStyle setFirstLineHeadIndent:1.0];
984 return aMutableParagraphStyle;
985}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400986
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500987#pragma mark - Actions
988
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400989- (void)acceptIncomingFile:(id)sender {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500990 auto interId = [(IMTableCellView*)[[sender superview] superview] interaction];
991 auto& inter = [self getCurrentConversation]->interactions.find(interId)->second;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400992 if (convModel_ && !convUid_.isEmpty()) {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500993 NSSavePanel* filePicker = [NSSavePanel savePanel];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400994 [filePicker setNameFieldStringValue:inter.body.toNSString()];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500995
996 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
997 const char* fullPath = [[filePicker URL] fileSystemRepresentation];
998 convModel_->acceptTransfer(convUid_, interId, fullPath);
999 }
1000 }
1001}
1002
Kateryna Kostiukae660fd2018-04-24 14:10:41 -04001003- (void)declineIncomingFile:(id)sender {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -05001004 auto inter = [(IMTableCellView*)[[sender superview] superview] interaction];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001005 if (convModel_ && !convUid_.isEmpty()) {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -05001006 convModel_->cancelTransfer(convUid_, inter);
1007 }
1008}
1009
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001010- (void)imagePreview:(id)sender {
1011 uint64_t interId;
1012 if ([[sender superview] isKindOfClass:[IMTableCellView class]]) {
1013 interId = [(IMTableCellView*)[sender superview] interaction];
1014 } else if ([[[sender superview] superview] isKindOfClass:[IMTableCellView class]]) {
1015 interId = [(IMTableCellView*)[[sender superview] superview] interaction];
1016 } else {
1017 return;
1018 }
1019 auto it = [self getCurrentConversation]->interactions.find(interId);
1020 if (it == [self getCurrentConversation]->interactions.end()) {
1021 return;
1022 }
1023 auto& interaction = it->second;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001024 NSString* name = interaction.body.toNSString();
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001025 if (([name rangeOfString:@"/"].location == NSNotFound)) {
1026 name = [self getDataTransferPath:interId];
1027 }
1028 previewImage = name;
Kateryna Kostiuk30c6ac22020-05-06 17:42:59 -04001029 if (!previewImage || previewImage.length <= 0) {
1030 return;
1031 }
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001032 if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) {
1033 [[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
1034 } else {
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001035 dispatch_async(dispatch_get_main_queue(), ^{
1036 [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:self];
1037 });
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001038 }
1039}
1040
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001041- (IBAction)sendMessage:(id)sender {
1042 NSString* text = self.message;
Kateryna Kostiuka8b6b562019-02-01 13:26:18 -05001043 unichar separatorChar = NSLineSeparatorCharacter;
1044 NSString *separatorString = [NSString stringWithCharacters:&separatorChar length:1];
1045 text = [text stringByReplacingOccurrencesOfString: separatorString withString: @"\n"];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001046 if (text && text.length > 0) {
1047 auto* conv = [self getCurrentConversation];
Kateryna Kostiuk0c068552020-03-30 09:48:17 -04001048 if (conv == nil)
1049 return;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001050 convModel_->sendMessage(convUid_, QString::fromNSString(text));
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001051 self.message = @"";
1052 if(sendPanelHeight.constant != SEND_PANEL_DEFAULT_HEIGHT) {
1053 sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
1054 messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
1055 [self scrollToBottom];
1056 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001057 if (composingMessage) {
1058 composingMessage = false;
1059 convModel_->setIsComposing(convUid_, composingMessage);
1060 }
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001061 }
1062}
1063
1064- (IBAction)openEmojy:(id)sender {
1065 [messageField.window makeFirstResponder: messageField];
1066 [[messageField currentEditor] moveToEndOfLine:nil];
1067 [NSApp orderFrontCharacterPalette: messageField];
1068}
1069
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001070- (IBAction)startVideoMessage:(id)sender
1071{
1072 [self startRecording:NO];
1073}
1074
1075- (IBAction)startAudioMessage:(id)sender
1076{
1077 [self startRecording:YES];
1078}
1079-(void) startRecording:(BOOL)isAudio {
1080 if (recordingController == nil) {
1081 recordingController = [[RecordFileVC alloc] initWithNibName:@"RecordFileVC" bundle:nil avModel: self->avModel];
1082 recordingController.delegate = self;
1083 }
1084 if(recordMessagePopover != nil)
1085 {
1086 [self closeRecordingView];
1087 return;
1088 }
1089 recordMessagePopover = [[NSPopover alloc] init];
1090 [recordingController prepareRecordingView: isAudio];
1091 [recordMessagePopover setContentSize: recordingController.view.frame.size];
1092 [recordMessagePopover setContentViewController:recordingController];
1093 [recordMessagePopover setAnimates:YES];
1094 NSButton *anchorButton = isAudio ? recordAudioButton : recordVideoButton;
1095 [recordMessagePopover showRelativeToRect: anchorButton.bounds
1096 ofView: anchorButton
1097 preferredEdge: NSMaxYEdge];
1098}
1099
1100-(void) sendFile:(NSString *) name withFilePath:(NSString *) path {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001101 convModel_->sendFile(convUid_, QString::fromNSString(path), QString::fromNSString(name));
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001102}
1103
1104-(void) closeRecordingView {
1105 if(recordMessagePopover != nil) {
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001106 [recordMessagePopover close];
1107 recordMessagePopover = nil;
Kateryna Kostiuka7404812019-10-28 12:24:46 -04001108 recordingController.stopRecordingView;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001109 }
1110}
1111
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001112- (IBAction)sendFile:(id)sender {
1113 NSOpenPanel* filePicker = [NSOpenPanel openPanel];
1114 [filePicker setCanChooseFiles:YES];
1115 [filePicker setCanChooseDirectories:NO];
1116 [filePicker setAllowsMultipleSelection:NO];
1117
1118 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
1119 if ([[filePicker URLs] count] == 1) {
1120 NSURL* url = [[filePicker URLs] objectAtIndex:0];
1121 const char* fullPath = [url fileSystemRepresentation];
1122 NSString* fileName = [url lastPathComponent];
1123 if (convModel_) {
1124 auto* conv = [self getCurrentConversation];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001125 convModel_->sendFile(convUid_, QString::fromStdString(fullPath), QString::fromNSString(fileName));
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001126 }
1127 }
1128 }
1129}
1130
1131
1132#pragma mark - NSTextFieldDelegate
1133
1134- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
1135{
1136 if (commandSelector == @selector(insertNewline:)) {
1137 if(self.message.length > 0) {
1138 [self sendMessage: nil];
1139 } else if(messagesBottomMargin.constant != SEND_PANEL_DEFAULT_HEIGHT) {
1140 sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
1141 messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
1142 [self scrollToBottom];
1143 }
1144 return YES;
1145 }
1146 return NO;
1147}
1148
1149- (void)controlTextDidChange:(NSNotification *)aNotification {
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001150 [self checkIfcomposingMsg];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001151 [self updateSendMessageHeight];
1152}
1153
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001154- (void) checkIfcomposingMsg {
1155 [self updateSendMessageHeight];
1156 BOOL haveText = [messageField.stringValue removeEmptyLinesAtBorders].length != 0;
1157 if (haveText != composingMessage) {
1158 composingMessage = haveText;
1159 convModel_->setIsComposing(convUid_, composingMessage);
1160 }
1161}
1162
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001163#pragma mark - QLPreviewPanelDataSource
1164
1165-(void)beginPreviewPanelControl:(QLPreviewPanel *)panel
1166{
1167 panel.dataSource = self;
1168}
1169
1170- (void)endPreviewPanelControl:(QLPreviewPanel *)panel {
1171 panel.dataSource = nil;
1172}
1173
1174-(BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel
1175{
1176 return YES;
1177}
1178
1179- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel {
1180 return 1;
1181}
1182
1183- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index {
Kateryna Kostiuk30c6ac22020-05-06 17:42:59 -04001184 try {
1185 return [NSURL fileURLWithPath: previewImage];
1186 } catch (NSException *exception) {
1187 nil;
1188 }
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001189}
1190
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04001191@end