blob: 364617c19bb03cb3a13cbf24d209b5ed21c891e6 [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 Kostiuk456cd812020-06-05 15:10:01 -040068 QMetaObject::Connection lastDisplayedChanged_;
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040069 NSString* previewImage;
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -050070 NSMutableDictionary *pendingMessagesToSend;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -040071 RecordFileVC * recordingController;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040072}
73
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040074@end
75
Anthony Léonardf2bb17d2018-02-15 17:18:09 -050076// Tags for view
77NSInteger const GENERIC_INT_TEXT_TAG = 100;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040078NSInteger const GENERIC_INT_TIME_TAG = 200;
79
80// views size
81CGFloat const GENERIC_CELL_HEIGHT = 60;
82CGFloat const TIME_BOX_HEIGHT = 34;
83CGFloat const MESSAGE_TEXT_PADDING = 10;
84CGFloat const MAX_TRANSFERED_IMAGE_SIZE = 250;
85CGFloat const BUBBLE_HEIGHT_FOR_TRANSFERED_FILE = 87;
Kateryna Kostiuk456cd812020-06-05 15:10:01 -040086CGFloat const DEFAULT_ROW_HEIGHT = 10;
87CGFloat const HEIGHT_FOR_COMPOSING_INDICATOR = 46;
Kateryna Kostiuka1201922020-04-20 11:59:35 -040088CGFloat const HEIGHT_DEFAULT = 34;
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -050089NSInteger const MEESAGE_MARGIN = 21;
90NSInteger const SEND_PANEL_DEFAULT_HEIGHT = 60;
91NSInteger const SEND_PANEL_MAX_HEIGHT = 120;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -050092
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -040093BOOL peerComposingMessage = false;
94BOOL composingMessage = false;
95
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040096@implementation MessagesVC
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040097
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040098
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040099//MessageBuble type
100typedef NS_ENUM(NSInteger, MessageSequencing) {
101 SINGLE_WITH_TIME = 0,
102 SINGLE_WITHOUT_TIME = 1,
103 FIRST_WITH_TIME = 2,
104 FIRST_WITHOUT_TIME = 3,
105 MIDDLE_IN_SEQUENCE = 5,
106 LAST_IN_SEQUENCE = 6,
107};
108
109- (void)awakeFromNib
110{
111 NSNib *cellNib = [[NSNib alloc] initWithNibNamed:@"MessageCells" bundle:nil];
112 [conversationView registerNib:cellNib forIdentifier:@"LeftIncomingFileView"];
113 [conversationView registerNib:cellNib forIdentifier:@"LeftOngoingFileView"];
114 [conversationView registerNib:cellNib forIdentifier:@"LeftFinishedFileView"];
115 [conversationView registerNib:cellNib forIdentifier:@"RightOngoingFileView"];
116 [conversationView registerNib:cellNib forIdentifier:@"RightFinishedFileView"];
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400117 [conversationView registerNib:cellNib forIdentifier:@"PeerComposingMsgView"];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500118 [[conversationView.enclosingScrollView contentView] setCopiesOnScroll:NO];
119 [messageField setFocusRingType:NSFocusRingTypeNone];
120 [conversationView setWantsLayer:YES];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400121}
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500122
123- (instancetype)initWithCoder:(NSCoder *)coder
124{
125 self = [super initWithCoder:coder];
126 if (self) {
127 pendingMessagesToSend = [[NSMutableDictionary alloc] init];
128 }
129 return self;
130}
131
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400132-(void) setAVModel: (lrc::api::AVModel*) avmodel {
133 avModel = avmodel;
134 if (recordingController == nil) {
135 recordingController = [[RecordFileVC alloc] initWithNibName:@"RecordFileVC" bundle:nil avModel: self->avModel];
136 recordingController.delegate = self;
137 }
138}
139
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500140- (void)setMessage:(NSString *)newValue {
141 _message = [newValue removeEmptyLinesAtBorders];
142}
143
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400144-(void) clearData {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400145 if (!convUid_.isEmpty()) {
146 pendingMessagesToSend[convUid_.toNSString()] = messageField.stringValue;
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500147 }
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400148 cachedConv_ = nil;
149 convUid_ = "";
150 convModel_ = nil;
151
152 QObject::disconnect(modelSortedSignal_);
153 QObject::disconnect(filterChangedSignal_);
154 QObject::disconnect(interactionStatusUpdatedSignal_);
155 QObject::disconnect(newInteractionSignal_);
Kateryna Kostiuk5acaefd2020-03-25 11:14:25 -0400156 QObject::disconnect(peerComposingMsgSignal_);
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400157 QObject::disconnect(lastDisplayedChanged_);
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400158 [self closeRecordingView];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400159}
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400160
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500161-(void) scrollToBottom {
162 CGRect visibleRect = [conversationView enclosingScrollView].contentView.visibleRect;
163 NSRange range = [conversationView rowsInRect:visibleRect];
164 NSIndexSet* visibleIndexes = [NSIndexSet indexSetWithIndexesInRange:range];
165 NSUInteger lastvisibleRow = [visibleIndexes lastIndex];
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400166 NSInteger numberOfRows = [conversationView numberOfRows];
167 if ((numberOfRows > 0) &&
168 lastvisibleRow > (numberOfRows - 5)) {
169 [conversationView scrollRowToVisible:numberOfRows - 1];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500170 }
171}
172
Anthony Léonard2382b562017-12-13 15:51:28 -0500173-(const lrc::api::conversation::Info*) getCurrentConversation
174{
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400175 if (convModel_ == nil || convUid_.isEmpty())
Anthony Léonard2382b562017-12-13 15:51:28 -0500176 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400177
Anthony Léonard2382b562017-12-13 15:51:28 -0500178 if (cachedConv_ != nil)
179 return cachedConv_;
Kateryna Kostiuk3541ae22020-08-17 12:26:14 -0400180 auto it = getConversationFromUid(convUid_, *convModel_);
181 if (conversationExists(it, *convModel_)) {
Anthony Léonard2382b562017-12-13 15:51:28 -0500182 cachedConv_ = &(*it);
Kateryna Kostiuk3541ae22020-08-17 12:26:14 -0400183 } else {
184 it = getSearchResultFromUid(convUid_, *convModel_);
185 if (searchResultExists(it, *convModel_)) {
186 cachedConv_ = &(*it);
187 }
188 }
Anthony Léonard2382b562017-12-13 15:51:28 -0500189 return cachedConv_;
190}
191
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400192-(void) reloadConversationForMessage:(uint64_t) uid updateSize:(BOOL) update {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400193 auto* conv = [self getCurrentConversation];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400194 if (conv == nil)
195 return;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400196 auto it = conv->interactions.find(uid);
197 if (it == conv->interactions.end()) {
198 return;
199 }
200 auto itIndex = distance(conv->interactions.begin(),it);
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400201 if (itIndex >= ([conversationView numberOfRows] - 1) || itIndex >= conv->interactions.size()) {
Kateryna Kostiuka1201922020-04-20 11:59:35 -0400202 return;
203 }
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400204 NSRange rangeToUpdate = NSMakeRange(itIndex, 2);
205 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndexesInRange:rangeToUpdate];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400206 //reload previous message to update bubbleview
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400207 if (itIndex > 0) {
208 auto previousIt = it;
209 previousIt--;
210 auto previousInteraction = previousIt->second;
211 if (previousInteraction.type == lrc::api::interaction::Type::TEXT) {
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400212 NSRange range = NSMakeRange(itIndex - 1, 3);
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400213 indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
214 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400215 }
216 if (update) {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400217 NSRange insertRange = NSMakeRange(itIndex, 1);
218 NSIndexSet* insertRangeSet = [NSIndexSet indexSetWithIndexesInRange:insertRange];
219 [conversationView removeRowsAtIndexes:insertRangeSet withAnimation:(NSTableViewAnimationEffectNone)];
220 [conversationView insertRowsAtIndexes:insertRangeSet withAnimation:(NSTableViewAnimationEffectNone)];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400221 }
222 [conversationView reloadDataForRowIndexes: indexSet
223 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400224 [self scrollToBottom];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400225}
226
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400227-(void)setConversationUid:(const QString&)convUid model:(lrc::api::ConversationModel *)model
Anthony Léonard2382b562017-12-13 15:51:28 -0500228{
229 if (convUid_ == convUid && convModel_ == model)
230 return;
231
232 cachedConv_ = nil;
233 convUid_ = convUid;
234 convModel_ = model;
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400235 peerComposingMessage = false;
236 composingMessage = false;
Anthony Léonard2382b562017-12-13 15:51:28 -0500237
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500238 // Signal triggered when messages are received or their status updated
239 QObject::disconnect(newInteractionSignal_);
240 QObject::disconnect(interactionStatusUpdatedSignal_);
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400241 QObject::disconnect(peerComposingMsgSignal_);
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400242 QObject::disconnect(lastDisplayedChanged_);
243 lastDisplayedChanged_ =
244 QObject::connect(convModel_,
245 &lrc::api::ConversationModel::displayedInteractionChanged,
246 [self](const QString &uid,
247 const QString &participantURI,
248 const uint64_t &previousUid,
249 const uint64_t &newdUid) {
250 if (uid != convUid_)
251 return;
252 [self reloadConversationForMessage:newdUid updateSize: NO];
253 [self reloadConversationForMessage:previousUid updateSize: NO];
254 });
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400255
256 peerComposingMsgSignal_ = QObject::connect(convModel_,
257 &lrc::api::ConversationModel::composingStatusChanged,
258 [self](const QString &uid,
259 const QString &contactUri,
260 bool isComposing) {
261 if (uid != convUid_)
262 return;
263 bool shouldUpdate = isComposing != peerComposingMessage;
264 if (!shouldUpdate) {
265 return;
266 }
267 // reload and update height for composing indicator
268 peerComposingMessage = isComposing;
269 auto* conv = [self getCurrentConversation];
270 if (conv == nil)
271 return;
272 auto row = [conversationView numberOfRows] - 1;
273 if (row < 0) {
274 return;
275 }
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400276 if(peerComposingMessage) {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400277 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:row];
278 [conversationView reloadDataForRowIndexes: indexSet
279 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400280 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400281 [self scrollToBottom];
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400282 } else {
283 //whait for possible incoming message to avoid view jumping
284 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400285 auto row = [conversationView numberOfRows] - 1;
286 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:row];
287 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
288 [conversationView reloadDataForRowIndexes: indexSet
289 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
290 [self scrollToBottom];
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400291 });
292 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400293 });
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500294 newInteractionSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::newInteraction,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400295 [self](const QString& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400296 if (uid != convUid_)
297 return;
298 cachedConv_ = nil;
299 peerComposingMessage = false;
300 [conversationView noteNumberOfRowsChanged];
301 [self reloadConversationForMessage:interactionId updateSize: YES];
302 [self scrollToBottom];
303 });
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500304 interactionStatusUpdatedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::interactionStatusUpdated,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400305 [self](const QString& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400306 if (uid != convUid_)
307 return;
308 cachedConv_ = nil;
309 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
310 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
311 convModel_->refreshFilter();
312 }
313 [self reloadConversationForMessage:interactionId updateSize: interaction.type == lrc::api::interaction::Type::DATA_TRANSFER];
314 [self scrollToBottom];
315 });
Anthony Léonard2382b562017-12-13 15:51:28 -0500316
317 // Signals tracking changes in conversation list, we need them as cached conversation can be invalid
318 // after a reordering.
319 QObject::disconnect(modelSortedSignal_);
320 QObject::disconnect(filterChangedSignal_);
321 modelSortedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelSorted,
322 [self](){
323 cachedConv_ = nil;
324 });
325 filterChangedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::filterChanged,
326 [self](){
327 cachedConv_ = nil;
328 });
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400329 if (pendingMessagesToSend[convUid_.toNSString()]) {
330 self.message = pendingMessagesToSend[convUid_.toNSString()];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500331 [self updateSendMessageHeight];
332 } else {
333 self.message = @"";
334 if(messagesBottomMargin.constant != SEND_PANEL_DEFAULT_HEIGHT) {
335 sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
336 messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
337 [self scrollToBottom];
338 }
339 }
340 conversationView.alphaValue = 0.0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500341 [conversationView reloadData];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400342 [conversationView scrollToEndOfDocument:nil];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500343 CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"];
344 fadeIn.fromValue = [NSNumber numberWithFloat:0.0];
345 fadeIn.toValue = [NSNumber numberWithFloat:1.0];
346 fadeIn.duration = 0.4f;
347
348 [conversationView.layer addAnimation:fadeIn forKey:fadeIn.keyPath];
349 conversationView.alphaValue = 1;
350 auto* conv = [self getCurrentConversation];
351
352 if (conv == nil)
353 return;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400354 try {
355 [sendFileButton setEnabled:(convModel_->owner.contactModel->getContact(conv->participants[0]).profileInfo.type != lrc::api::profile::Type::SIP)];
356 } catch (std::out_of_range& e) {
357 NSLog(@"contact out of range");
358 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400359}
360
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400361#pragma mark - configure cells
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400362
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400363-(NSTableCellView*) makeGenericInteractionViewForTableView:(NSTableView*)tableView withText:(NSString*)text andTime:(NSString*) time
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500364{
365 NSTableCellView* result = [tableView makeViewWithIdentifier:@"GenericInteractionView" owner:self];
366 NSTextField* textField = [result viewWithTag:GENERIC_INT_TEXT_TAG];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400367 NSTextField* timeField = [result viewWithTag:GENERIC_INT_TIME_TAG];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500368
369 // TODO: Fix symbol in LRC
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500370 NSString* fixedString = [[text stringByReplacingOccurrencesOfString:@"🕽" withString:@""] stringByReplacingOccurrencesOfString:@"📞" withString:@""];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500371 [textField setStringValue:fixedString];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400372 [timeField setStringValue:time];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500373
374 return result;
375}
376
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400377-(NSTableCellView*) configureViewforTransfer:(lrc::api::interaction::Info)interaction interactionID: (uint64_t) interactionID tableView:(NSTableView*)tableView
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500378{
379 IMTableCellView* result;
380
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400381 auto type = interaction.type;
382 auto status = interaction.status;
383
384 NSString* fileName = @"incoming file";
385
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500386 // First, view is created
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400387 if (!interaction.authorUri.isEmpty()) {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500388 switch (status) {
389 case lrc::api::interaction::Status::TRANSFER_CREATED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400390 case lrc::api::interaction::Status::TRANSFER_AWAITING_HOST: {
391 result = [tableView makeViewWithIdentifier:@"LeftIncomingFileView" owner: conversationView];
392 [result.acceptButton setAction:@selector(acceptIncomingFile:)];
393 [result.acceptButton setTarget:self];
394 [result.declineButton setAction:@selector(declineIncomingFile:)];
395 [result.declineButton setTarget:self];
396 break;}
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500397 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400398 case lrc::api::interaction::Status::TRANSFER_ONGOING: {
399 result = [tableView makeViewWithIdentifier:@"LeftOngoingFileView" owner:conversationView];
400 [result.progressIndicator startAnimation:conversationView];
401 [result.declineButton setAction:@selector(declineIncomingFile:)];
402 [result.declineButton setTarget:self];
403 break;}
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500404 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400405 result = [tableView makeViewWithIdentifier:@"LeftFinishedFileView" owner:conversationView];
406 [result.transferedFileName setAction:@selector(imagePreview:)];
407 [result.transferedFileName setTarget:self];
408 [result.transferedFileName.cell setHighlightsBy:NSContentsCellMask];
409 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500410 case lrc::api::interaction::Status::TRANSFER_CANCELED:
411 case lrc::api::interaction::Status::TRANSFER_ERROR:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400412 result = [tableView makeViewWithIdentifier:@"LeftFinishedFileView" owner:conversationView];
413 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500414 }
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400415 } else {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400416 NSString* fileName = @"sent file";
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500417 switch (status) {
418 case lrc::api::interaction::Status::TRANSFER_CREATED:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500419 case lrc::api::interaction::Status::TRANSFER_ONGOING:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400420 case lrc::api::interaction::Status::TRANSFER_AWAITING_PEER:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500421 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400422 result = [tableView makeViewWithIdentifier:@"RightOngoingFileView" owner:conversationView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500423 [result.progressIndicator startAnimation:nil];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400424 [result.declineButton setAction:@selector(declineIncomingFile:)];
425 [result.declineButton setTarget:self];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500426 break;
427 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400428 result = [tableView makeViewWithIdentifier:@"RightFinishedFileView" owner:conversationView];
429 [result.transferedFileName setAction:@selector(imagePreview:)];
430 [result.transferedFileName setTarget:self];
431 [result.transferedFileName.cell setHighlightsBy:NSContentsCellMask];
432 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500433 case lrc::api::interaction::Status::TRANSFER_CANCELED:
434 case lrc::api::interaction::Status::TRANSFER_ERROR:
Olivier Soldanoe521a182018-02-26 16:55:19 -0500435 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400436 result = [tableView makeViewWithIdentifier:@"RightFinishedFileView" owner:conversationView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500437 }
438 }
439
440 // Then status label is updated if needed
441 switch (status) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400442 [result.statusLabel setTextColor:[NSColor textColor]];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500443 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400444 [result.statusLabel setTextColor:[NSColor greenSuccessColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500445 [result.statusLabel setStringValue:NSLocalizedString(@"Success", @"File transfer successful label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500446 break;
447 case lrc::api::interaction::Status::TRANSFER_CANCELED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400448 [result.statusLabel setTextColor:[NSColor orangeColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500449 [result.statusLabel setStringValue:NSLocalizedString(@"Canceled", @"File transfer canceled label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500450 break;
451 case lrc::api::interaction::Status::TRANSFER_ERROR:
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400452 [result.statusLabel setTextColor:[NSColor errorTransferColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500453 [result.statusLabel setStringValue:NSLocalizedString(@"Failed", @"File transfer failed label")];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500454 break;
455 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400456 [result.statusLabel setTextColor:[NSColor textColor]];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500457 [result.statusLabel setStringValue:NSLocalizedString(@"Unjoinable", @"File transfer peer unjoinable label")];
458 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500459 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400460 result.transferedImage.image = nil;
Kateryna Kostiukeaf1bc82018-10-12 14:33:50 -0400461 [result.openImagebutton setHidden:YES];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400462 [result.msgBackground setHidden:NO];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400463 NSString* name = interaction.body.toNSString();
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400464 if (name.length > 0) {
Kateryna Kostiuk67735232018-05-10 15:05:32 -0400465 fileName = [name lastPathComponent];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400466 }
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400467 NSFont *nameFont = [NSFont userFontOfSize:14.0];
468 NSColor *nameColor = [NSColor textColor];
469 NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
470 paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
471 paragraphStyle.alignment = NSTextAlignmentLeft;
472 NSDictionary *nameAttr = [NSDictionary dictionaryWithObjectsAndKeys:nameFont,NSFontAttributeName,
473 nameColor,NSForegroundColorAttributeName,
474 paragraphStyle,NSParagraphStyleAttributeName, nil];
475 NSAttributedString* nameAttributedString = [[NSAttributedString alloc] initWithString:fileName attributes:nameAttr];
476 result.transferedFileName.attributedTitle = nameAttributedString;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400477 if (status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400478 NSColor *higlightColor = [NSColor grayColor];
479 NSDictionary *alternativeNametAttr = [NSDictionary dictionaryWithObjectsAndKeys:nameFont,NSFontAttributeName,
480 higlightColor,NSForegroundColorAttributeName,
481 paragraphStyle,NSParagraphStyleAttributeName, nil];
482 NSAttributedString* alternativeString = [[NSAttributedString alloc] initWithString:fileName attributes:alternativeNametAttr];
483 result.transferedFileName.attributedAlternateTitle = alternativeString;
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400484 NSImage* image = [self getImageForFilePath:name];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400485 if (([name rangeOfString:@"/"].location == NSNotFound)) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400486 image = [self getImageForFilePath:[self getDataTransferPath:interactionID]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400487 }
488 if(image != nil) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400489 result.transferedImage.image = image;
490 [result updateImageConstraintWithMax: MAX_TRANSFERED_IMAGE_SIZE];
491 [result.openImagebutton setAction:@selector(imagePreview:)];
492 [result.openImagebutton setTarget:self];
Kateryna Kostiukeaf1bc82018-10-12 14:33:50 -0400493 [result.openImagebutton setHidden:NO];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400494 }
495 }
496 [result setupForInteraction:interactionID];
497 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
498 NSString* timeString = [self timeForMessage: msgTime];
499 result.timeLabel.stringValue = timeString;
500 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
501 if (!isOutgoing) {
Kateryna Kostiukdc720842020-09-10 16:44:02 -0400502 @autoreleasepool {
503 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
504 auto* conv = [self getCurrentConversation];
505 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
506 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400507 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500508 return result;
509}
510
Anthony Léonard2382b562017-12-13 15:51:28 -0500511#pragma mark - NSTableViewDelegate methods
512- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400513{
514 return YES;
515}
516
Anthony Léonard2382b562017-12-13 15:51:28 -0500517- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400518{
Anthony Léonard2382b562017-12-13 15:51:28 -0500519 return NO;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400520}
521
Anthony Léonard2382b562017-12-13 15:51:28 -0500522- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400523{
Anthony Léonard2382b562017-12-13 15:51:28 -0500524 auto* conv = [self getCurrentConversation];
Anthony Léonard2382b562017-12-13 15:51:28 -0500525 if (conv == nil)
526 return nil;
527
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400528 IMTableCellView* result;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500529 auto it = conv->interactions.begin();
Kateryna Kostiuka1201922020-04-20 11:59:35 -0400530 auto size = [conversationView numberOfRows] - 1;
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400531
532 if (row > size || row > conv->interactions.size()) {
Kateryna Kostiuk0c068552020-03-30 09:48:17 -0400533 return [[NSView alloc] init];
534 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400535
536 if (row == size) {
Kateryna Kostiukb39ca192020-03-27 14:01:00 -0400537 if (size < 1) {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400538 return nil;
Kateryna Kostiukb39ca192020-03-27 14:01:00 -0400539 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400540 //last row peer composing view
541 result = [tableView makeViewWithIdentifier:@"PeerComposingMsgView" owner:conversationView];
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400542 result.alphaValue = 0;
543 [result animateCompozingIndicator: NO];
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400544 CGFloat alpha = peerComposingMessage ? 1 : 0;
545 CGFloat height = peerComposingMessage ? HEIGHT_FOR_COMPOSING_INDICATOR : DEFAULT_ROW_HEIGHT;
546 [result updateHeightConstraints: height];
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400547 if (alpha == 1) {
548 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
549 if (peerComposingMessage) {
550 result.alphaValue = alpha;
551 [result animateCompozingIndicator: YES];
552 }
553 });
554 }
555 return result;
556 }
Anthony Léonard2382b562017-12-13 15:51:28 -0500557
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500558 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500559
Kateryna Kostiuk0c068552020-03-30 09:48:17 -0400560 if (it == conv->interactions.end()) {
561 return [[NSView alloc] init];
562 }
563
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400564 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500565 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
566
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500567 switch (interaction.type) {
568 case lrc::api::interaction::Type::TEXT:
569 if (isOutgoing) {
570 result = [tableView makeViewWithIdentifier:@"RightMessageView" owner:self];
571 } else {
572 result = [tableView makeViewWithIdentifier:@"LeftMessageView" owner:self];
573 }
574 break;
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400575 case lrc::api::interaction::Type::DATA_TRANSFER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400576 return [self configureViewforTransfer:interaction interactionID: it->first tableView:tableView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500577 break;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500578 case lrc::api::interaction::Type::CONTACT:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400579 case lrc::api::interaction::Type::CALL: {
580 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
581 NSString* timeString = [self timeForMessage: msgTime];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400582 return [self makeGenericInteractionViewForTableView:tableView withText:interaction.body.toNSString() andTime:timeString];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400583 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500584 default: // If interaction is not of a known type
585 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400586 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400587 MessageSequencing sequence = [self computeSequencingFor:row];
588 BubbleType type = SINGLE;
589 if (sequence == FIRST_WITHOUT_TIME || sequence == FIRST_WITH_TIME) {
590 type = FIRST;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400591 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400592 if (sequence == MIDDLE_IN_SEQUENCE) {
593 type = MIDDLE;
594 }
595 if (sequence == LAST_IN_SEQUENCE) {
596 type = LAST;
597 }
598 result.msgBackground.type = type;
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400599 bool sendingFail = false;
600 [result.messageStatus setHidden:YES];
601 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
602 if (interaction.status == lrc::api::interaction::Status::SENDING) {
603 [result.messageStatus setHidden:NO];
604 [result.sendingMessageIndicator startAnimation:nil];
605 [result.messageFailed setHidden:YES];
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400606 } else if (interaction.status == lrc::api::interaction::Status::FAILURE) {
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400607 [result.messageStatus setHidden:NO];
608 [result.sendingMessageIndicator setHidden:YES];
609 [result.messageFailed setHidden:NO];
610 sendingFail = true;
611 }
612 }
613 [result setupForInteraction:it->first isFailed: sendingFail];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400614 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400615 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400616 [result.msgBackground setNeedsDisplay:YES];
617 [result setNeedsDisplay:YES];
618 [result.timeBox setNeedsDisplay:YES];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400619
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400620 NSString *text = interaction.body.toNSString();
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400621 text = [text removeEmptyLinesAtBorders];
622
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400623 NSMutableAttributedString* msgAttString =
Kateryna Kostiuk5a41a8d2020-05-13 09:05:56 -0400624 [[NSMutableAttributedString alloc] initWithString:text
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400625 attributes:[self messageAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400626
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400627 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400628
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400629 [result updateMessageConstraint:messageSize.width andHeight:messageSize.height timeIsVisible:shouldDisplayTime isTopPadding: shouldApplyPadding];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400630 [[result.msgView textStorage] appendAttributedString:msgAttString];
Anthony Léonard2382b562017-12-13 15:51:28 -0500631
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400632 NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
633 NSArray *matches = [linkDetector matchesInString:result.msgView.string options:0 range:NSMakeRange(0, result.msgView.string.length)];
634
635 [result.msgView.textStorage beginEditing];
636
637 for (NSTextCheckingResult *match in matches) {
638 if (!match.URL) continue;
639
640 NSDictionary *linkAttributes = @{
641 NSLinkAttributeName: match.URL,
642 };
643 [result.msgView.textStorage addAttributes:linkAttributes range:match.range];
644 }
645
646 [result.msgView.textStorage endEditing];
647
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400648 if (shouldDisplayTime) {
649 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
650 NSString* timeString = [self timeForMessage: msgTime];
651 result.timeLabel.stringValue = timeString;
Anthony Léonard64e19672018-01-18 16:40:34 -0500652 }
653
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400654 bool shouldDisplayAvatar = (sequence != MIDDLE_IN_SEQUENCE && sequence != FIRST_WITHOUT_TIME
655 && sequence != FIRST_WITH_TIME) ? YES : NO;
656 [result.photoView setHidden:!shouldDisplayAvatar];
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400657 BOOL showIndicator = convModel_->isLastDisplayed(convUid_, it->first, conv->participants.front());
658 [result.readIndicator setHidden: !showIndicator];
Kateryna Kostiukdc720842020-09-10 16:44:02 -0400659 @autoreleasepool {
660 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
661 auto image = QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)));
662 [result.readIndicator setImage:image];
663 if (!isOutgoing && shouldDisplayAvatar) {
664 [result.photoView setImage:image];
665 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400666 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400667 return result;
668}
669
Anthony Léonard2382b562017-12-13 15:51:28 -0500670- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400671{
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400672 try {
673 double someWidth = tableView.frame.size.width * 0.7;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400674
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400675 auto* conv = [self getCurrentConversation];
Anthony Léonard2382b562017-12-13 15:51:28 -0500676
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400677 if (conv == nil)
678 return HEIGHT_DEFAULT;
Kateryna Kostiuk0c068552020-03-30 09:48:17 -0400679
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400680 auto size = [conversationView numberOfRows] - 1;
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400681
682 if (row > size || row > conv->interactions.size()) {
683 return HEIGHT_DEFAULT;
684 }
685 if (row == size) {
686 return peerComposingMessage ? HEIGHT_FOR_COMPOSING_INDICATOR : DEFAULT_ROW_HEIGHT;
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400687 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400688
689 auto it = conv->interactions.begin();
690
691 std::advance(it, row);
692
693 if (it == conv->interactions.end()) {
694 return HEIGHT_DEFAULT;
695 }
696
697 auto interaction = it->second;
698
699 MessageSequencing sequence = [self computeSequencingFor:row];
700
701 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
702
703 if(interaction.type == lrc::api::interaction::Type::DATA_TRANSFER) {
704 if( interaction.status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
705 NSString* name = interaction.body.toNSString();
706 NSImage* image = [self getImageForFilePath:name];
707 if (([name rangeOfString:@"/"].location == NSNotFound)) {
708 image = [self getImageForFilePath:[self getDataTransferPath:it->first]];
709 }
710 if (image != nil) {
711 CGFloat widthScaleFactor = MAX_TRANSFERED_IMAGE_SIZE / image.size.width;
712 CGFloat heightScaleFactor = MAX_TRANSFERED_IMAGE_SIZE / image.size.height;
713 CGFloat heigt = 0;
714 if((widthScaleFactor >= 1) && (heightScaleFactor >= 1)) {
715 heigt = image.size.height;
716 } else {
717 CGFloat scale = MIN(widthScaleFactor, heightScaleFactor);
718 heigt = image.size.height * scale;
719 }
720 return heigt + TIME_BOX_HEIGHT;
721 }
722 }
723 return BUBBLE_HEIGHT_FOR_TRANSFERED_FILE + TIME_BOX_HEIGHT;
724 }
725
726 if(interaction.type == lrc::api::interaction::Type::CONTACT || interaction.type == lrc::api::interaction::Type::CALL)
727 return GENERIC_CELL_HEIGHT;
728
729 NSString *text = interaction.body.toNSString();
730 text = [text removeEmptyLinesAtBorders];
731
732 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
733 CGFloat singleLignMessageHeight = 15;
734
735 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
736
737 if (shouldDisplayTime) {
738 return MAX(messageSize.height + TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2,
739 TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2 + singleLignMessageHeight);
740 }
741 if(shouldApplyPadding) {
742 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2 + 15,
743 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2 + 15);
744 }
745 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2,
746 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2);
747 } catch (std::out_of_range& e) {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400748 return DEFAULT_ROW_HEIGHT;
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400749 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400750}
751
752#pragma mark - message view parameters
753
754-(NSString *) getDataTransferPath:(uint64_t)interactionId {
755 lrc::api::datatransfer::Info info = {};
756 convModel_->getTransferInfo(interactionId, info);
757 double convertData = static_cast<double>(info.totalSize);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400758 return info.path.toNSString();
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400759}
760
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400761-(NSImage*) getImageForFilePath: (NSString *) path {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400762 if (path.length <= 0) {return nil;}
763 if (![[NSFileManager defaultManager] fileExistsAtPath: path]) {return nil;}
764 NSImage* transferedImage = [[NSImage alloc] initWithContentsOfFile: path];
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400765 return transferedImage;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400766}
767
768-(CGSize) sizeFor:(NSString *) message maxWidth:(CGFloat) width {
769 CGFloat horizaontalMargin = 6;
Anthony Léonard2382b562017-12-13 15:51:28 -0500770 NSMutableAttributedString* msgAttString =
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400771 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@", message]
Anthony Léonard2382b562017-12-13 15:51:28 -0500772 attributes:[self messageAttributes]];
773
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400774 CGFloat finalWidth = MIN(msgAttString.size.width + horizaontalMargin * 2, width);
775 NSRect frame = NSMakeRect(0, 0, finalWidth, msgAttString.size.height);
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400776 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400777 [[tv textStorage] setAttributedString:msgAttString];
778 [tv sizeToFit];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400779 return tv.frame.size;
780}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400781
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400782-(MessageSequencing) computeSequencingFor:(NSInteger) row {
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400783 try {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400784 auto* conv = [self getCurrentConversation];
785 if (row >= conversationView.numberOfRows - 1 || row >= conv->interactions.size()) {
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400786 return SINGLE_WITHOUT_TIME;
787 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400788 if (conv == nil)
789 return SINGLE_WITHOUT_TIME;
790 auto it = conv->interactions.begin();
791 std::advance(it, row);
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400792 if (it == conv->interactions.end()) {
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400793 return SINGLE_WITHOUT_TIME;
794 }
795 auto interaction = it->second;
796 if (interaction.type != lrc::api::interaction::Type::TEXT) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400797 return SINGLE_WITH_TIME;
798 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400799 // first message in comversation
800 if (row == 0) {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400801 if (it == conv->interactions.end() || conv->interactions.size() < 2) {
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400802 return SINGLE_WITH_TIME;
803 }
804 auto nextIt = it;
805 nextIt++;
806 if (nextIt == conv->interactions.end()) {
807 return SINGLE_WITH_TIME;
808 }
809 auto nextInteraction = nextIt->second;
810 if ([self sequenceChangedFrom:interaction to: nextInteraction]) {
811 return SINGLE_WITH_TIME;
812 }
813 return FIRST_WITH_TIME;
814 }
815 // last message in comversation
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400816 if (row == conv->interactions.size() - 1) {
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400817 if(it == conv->interactions.begin()) {
818 return SINGLE_WITH_TIME;
819 }
820 auto previousIt = it;
821 previousIt--;
822 auto previousInteraction = previousIt->second;
823 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
824 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
825 if (!timeChanged && !authorChanged) {
826 return LAST_IN_SEQUENCE;
827 }
828 if (!timeChanged && authorChanged) {
829 return SINGLE_WITHOUT_TIME;
830 }
Kateryna Kostiukcd9397f2020-04-13 09:40:53 -0400831 return SINGLE_WITH_TIME;
832 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400833 // single message in comversation
834 if(it == conv->interactions.begin() || it == conv->interactions.end()) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400835 return SINGLE_WITH_TIME;
836 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400837 // message in the middle of conversation
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400838 auto previousIt = it;
839 previousIt--;
840 auto previousInteraction = previousIt->second;
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400841 auto nextIt = it;
842 nextIt++;
843 if (nextIt == conv->interactions.end()) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400844 return SINGLE_WITHOUT_TIME;
845 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400846 auto nextInteraction = nextIt->second;
847
848 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
849 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
850 bool sequenceWillChange = [self sequenceChangedFrom:interaction to: nextInteraction];
851 if (previousInteraction.type == lrc::api::interaction::Type::DATA_TRANSFER) {
852 if(!sequenceWillChange) {
853 return FIRST_WITH_TIME;
854 }
855 return SINGLE_WITH_TIME;
856 }
857 if (!sequenceWillChange) {
858 if (!timeChanged && !authorChanged) {
859 return MIDDLE_IN_SEQUENCE;
860 }
861 if (timeChanged) {
862 return FIRST_WITH_TIME;
863 }
864 return FIRST_WITHOUT_TIME;
865 } if (!timeChanged && !authorChanged) {
866 return LAST_IN_SEQUENCE;
867 } if (timeChanged) {
868 return SINGLE_WITH_TIME;
869 }
870 return SINGLE_WITHOUT_TIME;
871 } catch (std::out_of_range& e) {
Kateryna Kostiukcd9397f2020-04-13 09:40:53 -0400872 return SINGLE_WITHOUT_TIME;
873 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400874}
875
876-(bool) sequenceChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
877 return ([self sequenceTimeChangedFrom:firstInteraction to:secondInteraction] || [self sequenceAuthorChangedFrom:firstInteraction to:secondInteraction]);
878}
879
880-(bool) sequenceTimeChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
881 bool timeChanged = NO;
882 NSDate* firstMessageTime = [NSDate dateWithTimeIntervalSince1970:firstInteraction.timestamp];
883 NSDate* secondMessageTime = [NSDate dateWithTimeIntervalSince1970:secondInteraction.timestamp];
884 bool hourComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitHour];
885 bool minutComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitMinute];
886 if(hourComp != NSOrderedSame || minutComp != NSOrderedSame) {
887 timeChanged = YES;
888 }
889 return timeChanged;
890}
891
892-(bool) sequenceAuthorChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
893 bool authorChanged = YES;
894 bool isOutgoing = lrc::api::interaction::isOutgoing(firstInteraction);
895 if ((secondInteraction.type == lrc::api::interaction::Type::TEXT) && (isOutgoing == lrc::api::interaction::isOutgoing(secondInteraction))) {
896 authorChanged = NO;
897 }
898 return authorChanged;
899}
900
901-(NSString *)timeForMessage:(NSDate*) msgTime {
902 NSDate *today = [NSDate date];
903 NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
Kateryna Kostiukaf6d5e22018-06-12 15:00:00 -0400904 [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:[[NSLocale currentLocale] localeIdentifier]]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400905 if ([[NSCalendar currentCalendar] compareDate:today
906 toDate:msgTime
907 toUnitGranularity:NSCalendarUnitYear]!= NSOrderedSame) {
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500908 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterLongStyle timeStyle:NSDateFormatterMediumStyle];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400909 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400910 if ([[NSCalendar currentCalendar] compareDate:today
911 toDate:msgTime
912 toUnitGranularity:NSCalendarUnitDay]!= NSOrderedSame ||
913 [[NSCalendar currentCalendar] compareDate:today
914 toDate:msgTime
915 toUnitGranularity:NSCalendarUnitMonth]!= NSOrderedSame) {
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500916 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterShortStyle];
917 }
918 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterShortStyle];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400919}
920
Kateryna Kostiukf6317422018-09-27 17:08:20 -0400921- (void) updateSendMessageHeight {
922 NSAttributedString *msgAttString = messageField.attributedStringValue;
923 NSRect frame = NSMakeRect(0, 0, messageField.frame.size.width, msgAttString.size.height);
924 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
925 [[tv textStorage] setAttributedString:msgAttString];
926 [tv sizeToFit];
927 CGFloat height = tv.frame.size.height + MEESAGE_MARGIN * 2;
928 CGFloat newHeight = MIN(SEND_PANEL_MAX_HEIGHT, MAX(SEND_PANEL_DEFAULT_HEIGHT, height));
929 if(messagesBottomMargin.constant == newHeight) {
930 return;
931 }
932 messagesBottomMargin.constant = newHeight;
933 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
934 [self scrollToBottom];
935 sendPanelHeight.constant = newHeight;
936 });
937}
938
Anthony Léonard2382b562017-12-13 15:51:28 -0500939#pragma mark - NSTableViewDataSource
940
941- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
942{
943 auto* conv = [self getCurrentConversation];
944
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400945 // return conversation +1 view for composing indicator
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500946 if (conv)
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400947 return conv->interactions.size() + 1;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500948 else
949 return 0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500950}
951
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400952#pragma mark - Text formatting
953
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400954- (NSMutableDictionary*) messageAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400955{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400956 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400957 attrs[NSForegroundColorAttributeName] = [NSColor labelColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400958 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400959 return attrs;
960}
961
962- (NSParagraphStyle*) paragraphStyle
963{
964 /*
965 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
966 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
967 to copy, we use the default one.
968
969 The default values supplied by the default NSParagraphStyle are:
970 Alignment NSNaturalTextAlignment
971 Tab stops 12 left-aligned tabs, spaced by 28.0 points
972 Line break mode NSLineBreakByWordWrapping
973 All others 0.0
974 */
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400975 NSMutableParagraphStyle* aMutableParagraphStyle =
976 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
977 [aMutableParagraphStyle setHeadIndent:1.0];
978 [aMutableParagraphStyle setFirstLineHeadIndent:1.0];
979 return aMutableParagraphStyle;
980}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400981
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500982#pragma mark - Actions
983
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400984- (void)acceptIncomingFile:(id)sender {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400985 auto interId = [(IMTableCellView*)[[[[[[sender superview] superview] superview] superview] superview] superview] interaction];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500986 auto& inter = [self getCurrentConversation]->interactions.find(interId)->second;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400987 if (convModel_ && !convUid_.isEmpty()) {
Kateryna Kostiuk26405ac2020-05-27 14:25:13 -0400988 convModel_->acceptTransfer(convUid_, interId);
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500989 }
990}
991
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400992- (void)declineIncomingFile:(id)sender {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400993 auto inter = [(IMTableCellView*)[[[[[[sender superview] superview] superview] superview] superview] superview] interaction];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400994 if (convModel_ && !convUid_.isEmpty()) {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500995 convModel_->cancelTransfer(convUid_, inter);
996 }
997}
998
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400999- (void)imagePreview:(id)sender {
1000 uint64_t interId;
Kateryna Kostiuk456cd812020-06-05 15:10:01 -04001001 if ([[[[[[sender superview] superview] superview] superview] superview] isKindOfClass:[IMTableCellView class]]) {
1002 interId = [(IMTableCellView*)[[[[[sender superview] superview] superview] superview] superview] interaction];
1003 } else if ([[[[[sender superview] superview] superview] superview] isKindOfClass:[IMTableCellView class]]) {
1004 interId = [(IMTableCellView*)[[[[sender superview] superview] superview] superview] interaction];
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001005 } else {
1006 return;
1007 }
1008 auto it = [self getCurrentConversation]->interactions.find(interId);
1009 if (it == [self getCurrentConversation]->interactions.end()) {
1010 return;
1011 }
1012 auto& interaction = it->second;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001013 NSString* name = interaction.body.toNSString();
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001014 if (([name rangeOfString:@"/"].location == NSNotFound)) {
1015 name = [self getDataTransferPath:interId];
1016 }
1017 previewImage = name;
Kateryna Kostiuk30c6ac22020-05-06 17:42:59 -04001018 if (!previewImage || previewImage.length <= 0) {
1019 return;
1020 }
Kateryna Kostiuka116a0a2020-07-09 11:39:22 -04001021 [self addToResponderChain];
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001022 if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) {
1023 [[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
1024 } else {
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001025 dispatch_async(dispatch_get_main_queue(), ^{
1026 [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:self];
1027 });
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001028 }
1029}
1030
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001031- (IBAction)sendMessage:(id)sender {
1032 NSString* text = self.message;
Kateryna Kostiuka8b6b562019-02-01 13:26:18 -05001033 unichar separatorChar = NSLineSeparatorCharacter;
1034 NSString *separatorString = [NSString stringWithCharacters:&separatorChar length:1];
1035 text = [text stringByReplacingOccurrencesOfString: separatorString withString: @"\n"];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001036 if (text && text.length > 0) {
1037 auto* conv = [self getCurrentConversation];
Kateryna Kostiuk0c068552020-03-30 09:48:17 -04001038 if (conv == nil)
1039 return;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001040 convModel_->sendMessage(convUid_, QString::fromNSString(text));
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001041 self.message = @"";
1042 if(sendPanelHeight.constant != SEND_PANEL_DEFAULT_HEIGHT) {
1043 sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
1044 messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
1045 [self scrollToBottom];
1046 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001047 if (composingMessage) {
1048 composingMessage = false;
1049 convModel_->setIsComposing(convUid_, composingMessage);
1050 }
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001051 }
1052}
1053
1054- (IBAction)openEmojy:(id)sender {
1055 [messageField.window makeFirstResponder: messageField];
1056 [[messageField currentEditor] moveToEndOfLine:nil];
1057 [NSApp orderFrontCharacterPalette: messageField];
1058}
1059
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001060- (IBAction)startVideoMessage:(id)sender
1061{
1062 [self startRecording:NO];
1063}
1064
1065- (IBAction)startAudioMessage:(id)sender
1066{
1067 [self startRecording:YES];
1068}
1069-(void) startRecording:(BOOL)isAudio {
1070 if (recordingController == nil) {
1071 recordingController = [[RecordFileVC alloc] initWithNibName:@"RecordFileVC" bundle:nil avModel: self->avModel];
1072 recordingController.delegate = self;
1073 }
1074 if(recordMessagePopover != nil)
1075 {
1076 [self closeRecordingView];
1077 return;
1078 }
1079 recordMessagePopover = [[NSPopover alloc] init];
1080 [recordingController prepareRecordingView: isAudio];
1081 [recordMessagePopover setContentSize: recordingController.view.frame.size];
1082 [recordMessagePopover setContentViewController:recordingController];
1083 [recordMessagePopover setAnimates:YES];
1084 NSButton *anchorButton = isAudio ? recordAudioButton : recordVideoButton;
1085 [recordMessagePopover showRelativeToRect: anchorButton.bounds
1086 ofView: anchorButton
1087 preferredEdge: NSMaxYEdge];
1088}
1089
1090-(void) sendFile:(NSString *) name withFilePath:(NSString *) path {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001091 convModel_->sendFile(convUid_, QString::fromNSString(path), QString::fromNSString(name));
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001092}
1093
1094-(void) closeRecordingView {
1095 if(recordMessagePopover != nil) {
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001096 [recordMessagePopover close];
1097 recordMessagePopover = nil;
Kateryna Kostiuka7404812019-10-28 12:24:46 -04001098 recordingController.stopRecordingView;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001099 }
1100}
1101
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001102- (IBAction)sendFile:(id)sender {
1103 NSOpenPanel* filePicker = [NSOpenPanel openPanel];
1104 [filePicker setCanChooseFiles:YES];
1105 [filePicker setCanChooseDirectories:NO];
1106 [filePicker setAllowsMultipleSelection:NO];
1107
1108 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
1109 if ([[filePicker URLs] count] == 1) {
1110 NSURL* url = [[filePicker URLs] objectAtIndex:0];
1111 const char* fullPath = [url fileSystemRepresentation];
1112 NSString* fileName = [url lastPathComponent];
1113 if (convModel_) {
1114 auto* conv = [self getCurrentConversation];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001115 convModel_->sendFile(convUid_, QString::fromStdString(fullPath), QString::fromNSString(fileName));
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001116 }
1117 }
1118 }
1119}
1120
1121
1122#pragma mark - NSTextFieldDelegate
1123
1124- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
1125{
1126 if (commandSelector == @selector(insertNewline:)) {
1127 if(self.message.length > 0) {
1128 [self sendMessage: nil];
1129 } else if(messagesBottomMargin.constant != SEND_PANEL_DEFAULT_HEIGHT) {
1130 sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
1131 messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
1132 [self scrollToBottom];
1133 }
1134 return YES;
1135 }
1136 return NO;
1137}
1138
1139- (void)controlTextDidChange:(NSNotification *)aNotification {
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001140 [self checkIfcomposingMsg];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001141 [self updateSendMessageHeight];
1142}
1143
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001144- (void) checkIfcomposingMsg {
1145 [self updateSendMessageHeight];
1146 BOOL haveText = [messageField.stringValue removeEmptyLinesAtBorders].length != 0;
1147 if (haveText != composingMessage) {
1148 composingMessage = haveText;
1149 convModel_->setIsComposing(convUid_, composingMessage);
1150 }
1151}
1152
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001153#pragma mark - QLPreviewPanelDataSource
1154
1155-(void)beginPreviewPanelControl:(QLPreviewPanel *)panel
1156{
1157 panel.dataSource = self;
1158}
1159
1160- (void)endPreviewPanelControl:(QLPreviewPanel *)panel {
1161 panel.dataSource = nil;
Kateryna Kostiuka116a0a2020-07-09 11:39:22 -04001162 [self removeFromResponderChain];
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001163}
1164
1165-(BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel
1166{
1167 return YES;
1168}
1169
1170- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel {
1171 return 1;
1172}
1173
1174- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index {
Kateryna Kostiuka116a0a2020-07-09 11:39:22 -04001175 if (previewImage == nil) {
1176 return nil;
1177 }
Kateryna Kostiuk30c6ac22020-05-06 17:42:59 -04001178 try {
1179 return [NSURL fileURLWithPath: previewImage];
1180 } catch (NSException *exception) {
1181 nil;
1182 }
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001183}
1184
Kateryna Kostiuka116a0a2020-07-09 11:39:22 -04001185- (void)addToResponderChain {
1186 if (conversationView.window &&
1187 ![[conversationView.window nextResponder] isEqual:self]) {
1188 NSResponder * aNextResponder = [conversationView.window nextResponder];
1189 [conversationView.window setNextResponder:self];
1190 }
1191}
1192
1193
1194- (void)removeFromResponderChain {
1195 if (conversationView.window &&
1196 [[conversationView.window nextResponder] isEqual:self]) {
1197 NSResponder * aNextResponder = [conversationView.window nextResponder];
1198 [conversationView.window setNextResponder:[self nextResponder]];
1199 }
1200}
1201
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04001202@end