blob: b6cd85727e7e8ecd3b7098db3b5dde414d07e940 [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 Kostiuk58276bc2017-06-07 08:50:48 -0400180
Anthony Léonard6f819752018-01-05 09:53:40 -0500181 auto it = getConversationFromUid(convUid_, *convModel_);
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400182 if (it != convModel_->allFilteredConversations().end())
Anthony Léonard2382b562017-12-13 15:51:28 -0500183 cachedConv_ = &(*it);
184
185 return cachedConv_;
186}
187
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400188-(void) reloadConversationForMessage:(uint64_t) uid updateSize:(BOOL) update {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400189 auto* conv = [self getCurrentConversation];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400190 if (conv == nil)
191 return;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400192 auto it = conv->interactions.find(uid);
193 if (it == conv->interactions.end()) {
194 return;
195 }
196 auto itIndex = distance(conv->interactions.begin(),it);
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400197 if (itIndex >= ([conversationView numberOfRows] - 1) || itIndex >= conv->interactions.size()) {
Kateryna Kostiuka1201922020-04-20 11:59:35 -0400198 return;
199 }
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400200 NSRange rangeToUpdate = NSMakeRange(itIndex, 2);
201 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndexesInRange:rangeToUpdate];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400202 //reload previous message to update bubbleview
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400203 if (itIndex > 0) {
204 auto previousIt = it;
205 previousIt--;
206 auto previousInteraction = previousIt->second;
207 if (previousInteraction.type == lrc::api::interaction::Type::TEXT) {
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400208 NSRange range = NSMakeRange(itIndex - 1, 3);
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400209 indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
210 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400211 }
212 if (update) {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400213 NSRange insertRange = NSMakeRange(itIndex, 1);
214 NSIndexSet* insertRangeSet = [NSIndexSet indexSetWithIndexesInRange:insertRange];
215 [conversationView removeRowsAtIndexes:insertRangeSet withAnimation:(NSTableViewAnimationEffectNone)];
216 [conversationView insertRowsAtIndexes:insertRangeSet withAnimation:(NSTableViewAnimationEffectNone)];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400217 }
218 [conversationView reloadDataForRowIndexes: indexSet
219 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400220 [self scrollToBottom];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400221}
222
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400223-(void)setConversationUid:(const QString&)convUid model:(lrc::api::ConversationModel *)model
Anthony Léonard2382b562017-12-13 15:51:28 -0500224{
225 if (convUid_ == convUid && convModel_ == model)
226 return;
227
228 cachedConv_ = nil;
229 convUid_ = convUid;
230 convModel_ = model;
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400231 peerComposingMessage = false;
232 composingMessage = false;
Anthony Léonard2382b562017-12-13 15:51:28 -0500233
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500234 // Signal triggered when messages are received or their status updated
235 QObject::disconnect(newInteractionSignal_);
236 QObject::disconnect(interactionStatusUpdatedSignal_);
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400237 QObject::disconnect(peerComposingMsgSignal_);
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400238 QObject::disconnect(lastDisplayedChanged_);
239 lastDisplayedChanged_ =
240 QObject::connect(convModel_,
241 &lrc::api::ConversationModel::displayedInteractionChanged,
242 [self](const QString &uid,
243 const QString &participantURI,
244 const uint64_t &previousUid,
245 const uint64_t &newdUid) {
246 if (uid != convUid_)
247 return;
248 [self reloadConversationForMessage:newdUid updateSize: NO];
249 [self reloadConversationForMessage:previousUid updateSize: NO];
250 });
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400251
252 peerComposingMsgSignal_ = QObject::connect(convModel_,
253 &lrc::api::ConversationModel::composingStatusChanged,
254 [self](const QString &uid,
255 const QString &contactUri,
256 bool isComposing) {
257 if (uid != convUid_)
258 return;
259 bool shouldUpdate = isComposing != peerComposingMessage;
260 if (!shouldUpdate) {
261 return;
262 }
263 // reload and update height for composing indicator
264 peerComposingMessage = isComposing;
265 auto* conv = [self getCurrentConversation];
266 if (conv == nil)
267 return;
268 auto row = [conversationView numberOfRows] - 1;
269 if (row < 0) {
270 return;
271 }
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400272 if(peerComposingMessage) {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400273 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:row];
274 [conversationView reloadDataForRowIndexes: indexSet
275 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400276 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400277 [self scrollToBottom];
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400278 } else {
279 //whait for possible incoming message to avoid view jumping
280 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400281 auto row = [conversationView numberOfRows] - 1;
282 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:row];
283 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
284 [conversationView reloadDataForRowIndexes: indexSet
285 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
286 [self scrollToBottom];
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400287 });
288 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400289 });
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500290 newInteractionSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::newInteraction,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400291 [self](const QString& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400292 if (uid != convUid_)
293 return;
294 cachedConv_ = nil;
295 peerComposingMessage = false;
296 [conversationView noteNumberOfRowsChanged];
297 [self reloadConversationForMessage:interactionId updateSize: YES];
298 [self scrollToBottom];
299 });
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500300 interactionStatusUpdatedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::interactionStatusUpdated,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400301 [self](const QString& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400302 if (uid != convUid_)
303 return;
304 cachedConv_ = nil;
305 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
306 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
307 convModel_->refreshFilter();
308 }
309 [self reloadConversationForMessage:interactionId updateSize: interaction.type == lrc::api::interaction::Type::DATA_TRANSFER];
310 [self scrollToBottom];
311 });
Anthony Léonard2382b562017-12-13 15:51:28 -0500312
313 // Signals tracking changes in conversation list, we need them as cached conversation can be invalid
314 // after a reordering.
315 QObject::disconnect(modelSortedSignal_);
316 QObject::disconnect(filterChangedSignal_);
317 modelSortedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelSorted,
318 [self](){
319 cachedConv_ = nil;
320 });
321 filterChangedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::filterChanged,
322 [self](){
323 cachedConv_ = nil;
324 });
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400325 if (pendingMessagesToSend[convUid_.toNSString()]) {
326 self.message = pendingMessagesToSend[convUid_.toNSString()];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500327 [self updateSendMessageHeight];
328 } else {
329 self.message = @"";
330 if(messagesBottomMargin.constant != SEND_PANEL_DEFAULT_HEIGHT) {
331 sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
332 messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
333 [self scrollToBottom];
334 }
335 }
336 conversationView.alphaValue = 0.0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500337 [conversationView reloadData];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400338 [conversationView scrollToEndOfDocument:nil];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500339 CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"];
340 fadeIn.fromValue = [NSNumber numberWithFloat:0.0];
341 fadeIn.toValue = [NSNumber numberWithFloat:1.0];
342 fadeIn.duration = 0.4f;
343
344 [conversationView.layer addAnimation:fadeIn forKey:fadeIn.keyPath];
345 conversationView.alphaValue = 1;
346 auto* conv = [self getCurrentConversation];
347
348 if (conv == nil)
349 return;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400350 try {
351 [sendFileButton setEnabled:(convModel_->owner.contactModel->getContact(conv->participants[0]).profileInfo.type != lrc::api::profile::Type::SIP)];
352 } catch (std::out_of_range& e) {
353 NSLog(@"contact out of range");
354 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400355}
356
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400357#pragma mark - configure cells
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400358
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400359-(NSTableCellView*) makeGenericInteractionViewForTableView:(NSTableView*)tableView withText:(NSString*)text andTime:(NSString*) time
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500360{
361 NSTableCellView* result = [tableView makeViewWithIdentifier:@"GenericInteractionView" owner:self];
362 NSTextField* textField = [result viewWithTag:GENERIC_INT_TEXT_TAG];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400363 NSTextField* timeField = [result viewWithTag:GENERIC_INT_TIME_TAG];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500364
365 // TODO: Fix symbol in LRC
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500366 NSString* fixedString = [[text stringByReplacingOccurrencesOfString:@"🕽" withString:@""] stringByReplacingOccurrencesOfString:@"📞" withString:@""];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500367 [textField setStringValue:fixedString];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400368 [timeField setStringValue:time];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500369
370 return result;
371}
372
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400373-(NSTableCellView*) configureViewforTransfer:(lrc::api::interaction::Info)interaction interactionID: (uint64_t) interactionID tableView:(NSTableView*)tableView
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500374{
375 IMTableCellView* result;
376
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400377 auto type = interaction.type;
378 auto status = interaction.status;
379
380 NSString* fileName = @"incoming file";
381
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500382 // First, view is created
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400383 if (!interaction.authorUri.isEmpty()) {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500384 switch (status) {
385 case lrc::api::interaction::Status::TRANSFER_CREATED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400386 case lrc::api::interaction::Status::TRANSFER_AWAITING_HOST: {
387 result = [tableView makeViewWithIdentifier:@"LeftIncomingFileView" owner: conversationView];
388 [result.acceptButton setAction:@selector(acceptIncomingFile:)];
389 [result.acceptButton setTarget:self];
390 [result.declineButton setAction:@selector(declineIncomingFile:)];
391 [result.declineButton setTarget:self];
392 break;}
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500393 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400394 case lrc::api::interaction::Status::TRANSFER_ONGOING: {
395 result = [tableView makeViewWithIdentifier:@"LeftOngoingFileView" owner:conversationView];
396 [result.progressIndicator startAnimation:conversationView];
397 [result.declineButton setAction:@selector(declineIncomingFile:)];
398 [result.declineButton setTarget:self];
399 break;}
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500400 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400401 result = [tableView makeViewWithIdentifier:@"LeftFinishedFileView" owner:conversationView];
402 [result.transferedFileName setAction:@selector(imagePreview:)];
403 [result.transferedFileName setTarget:self];
404 [result.transferedFileName.cell setHighlightsBy:NSContentsCellMask];
405 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500406 case lrc::api::interaction::Status::TRANSFER_CANCELED:
407 case lrc::api::interaction::Status::TRANSFER_ERROR:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400408 result = [tableView makeViewWithIdentifier:@"LeftFinishedFileView" owner:conversationView];
409 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500410 }
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400411 } else {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400412 NSString* fileName = @"sent file";
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500413 switch (status) {
414 case lrc::api::interaction::Status::TRANSFER_CREATED:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500415 case lrc::api::interaction::Status::TRANSFER_ONGOING:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400416 case lrc::api::interaction::Status::TRANSFER_AWAITING_PEER:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500417 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400418 result = [tableView makeViewWithIdentifier:@"RightOngoingFileView" owner:conversationView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500419 [result.progressIndicator startAnimation:nil];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400420 [result.declineButton setAction:@selector(declineIncomingFile:)];
421 [result.declineButton setTarget:self];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500422 break;
423 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400424 result = [tableView makeViewWithIdentifier:@"RightFinishedFileView" owner:conversationView];
425 [result.transferedFileName setAction:@selector(imagePreview:)];
426 [result.transferedFileName setTarget:self];
427 [result.transferedFileName.cell setHighlightsBy:NSContentsCellMask];
428 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500429 case lrc::api::interaction::Status::TRANSFER_CANCELED:
430 case lrc::api::interaction::Status::TRANSFER_ERROR:
Olivier Soldanoe521a182018-02-26 16:55:19 -0500431 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400432 result = [tableView makeViewWithIdentifier:@"RightFinishedFileView" owner:conversationView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500433 }
434 }
435
436 // Then status label is updated if needed
437 switch (status) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400438 [result.statusLabel setTextColor:[NSColor textColor]];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500439 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400440 [result.statusLabel setTextColor:[NSColor greenSuccessColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500441 [result.statusLabel setStringValue:NSLocalizedString(@"Success", @"File transfer successful label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500442 break;
443 case lrc::api::interaction::Status::TRANSFER_CANCELED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400444 [result.statusLabel setTextColor:[NSColor orangeColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500445 [result.statusLabel setStringValue:NSLocalizedString(@"Canceled", @"File transfer canceled label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500446 break;
447 case lrc::api::interaction::Status::TRANSFER_ERROR:
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400448 [result.statusLabel setTextColor:[NSColor errorTransferColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500449 [result.statusLabel setStringValue:NSLocalizedString(@"Failed", @"File transfer failed label")];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500450 break;
451 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400452 [result.statusLabel setTextColor:[NSColor textColor]];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500453 [result.statusLabel setStringValue:NSLocalizedString(@"Unjoinable", @"File transfer peer unjoinable label")];
454 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500455 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400456 result.transferedImage.image = nil;
Kateryna Kostiukeaf1bc82018-10-12 14:33:50 -0400457 [result.openImagebutton setHidden:YES];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400458 [result.msgBackground setHidden:NO];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400459 NSString* name = interaction.body.toNSString();
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400460 if (name.length > 0) {
Kateryna Kostiuk67735232018-05-10 15:05:32 -0400461 fileName = [name lastPathComponent];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400462 }
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400463 NSFont *nameFont = [NSFont userFontOfSize:14.0];
464 NSColor *nameColor = [NSColor textColor];
465 NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
466 paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
467 paragraphStyle.alignment = NSTextAlignmentLeft;
468 NSDictionary *nameAttr = [NSDictionary dictionaryWithObjectsAndKeys:nameFont,NSFontAttributeName,
469 nameColor,NSForegroundColorAttributeName,
470 paragraphStyle,NSParagraphStyleAttributeName, nil];
471 NSAttributedString* nameAttributedString = [[NSAttributedString alloc] initWithString:fileName attributes:nameAttr];
472 result.transferedFileName.attributedTitle = nameAttributedString;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400473 if (status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400474 NSColor *higlightColor = [NSColor grayColor];
475 NSDictionary *alternativeNametAttr = [NSDictionary dictionaryWithObjectsAndKeys:nameFont,NSFontAttributeName,
476 higlightColor,NSForegroundColorAttributeName,
477 paragraphStyle,NSParagraphStyleAttributeName, nil];
478 NSAttributedString* alternativeString = [[NSAttributedString alloc] initWithString:fileName attributes:alternativeNametAttr];
479 result.transferedFileName.attributedAlternateTitle = alternativeString;
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400480 NSImage* image = [self getImageForFilePath:name];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400481 if (([name rangeOfString:@"/"].location == NSNotFound)) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400482 image = [self getImageForFilePath:[self getDataTransferPath:interactionID]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400483 }
484 if(image != nil) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400485 result.transferedImage.image = image;
486 [result updateImageConstraintWithMax: MAX_TRANSFERED_IMAGE_SIZE];
487 [result.openImagebutton setAction:@selector(imagePreview:)];
488 [result.openImagebutton setTarget:self];
Kateryna Kostiukeaf1bc82018-10-12 14:33:50 -0400489 [result.openImagebutton setHidden:NO];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400490 }
491 }
492 [result setupForInteraction:interactionID];
493 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
494 NSString* timeString = [self timeForMessage: msgTime];
495 result.timeLabel.stringValue = timeString;
496 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
497 if (!isOutgoing) {
498 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
499 auto* conv = [self getCurrentConversation];
500 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
501 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500502 return result;
503}
504
Anthony Léonard2382b562017-12-13 15:51:28 -0500505#pragma mark - NSTableViewDelegate methods
506- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400507{
508 return YES;
509}
510
Anthony Léonard2382b562017-12-13 15:51:28 -0500511- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400512{
Anthony Léonard2382b562017-12-13 15:51:28 -0500513 return NO;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400514}
515
Anthony Léonard2382b562017-12-13 15:51:28 -0500516- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400517{
Anthony Léonard2382b562017-12-13 15:51:28 -0500518 auto* conv = [self getCurrentConversation];
Anthony Léonard2382b562017-12-13 15:51:28 -0500519 if (conv == nil)
520 return nil;
521
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400522 IMTableCellView* result;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500523 auto it = conv->interactions.begin();
Kateryna Kostiuka1201922020-04-20 11:59:35 -0400524 auto size = [conversationView numberOfRows] - 1;
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400525
526 if (row > size || row > conv->interactions.size()) {
Kateryna Kostiuk0c068552020-03-30 09:48:17 -0400527 return [[NSView alloc] init];
528 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400529
530 if (row == size) {
Kateryna Kostiukb39ca192020-03-27 14:01:00 -0400531 if (size < 1) {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400532 return nil;
Kateryna Kostiukb39ca192020-03-27 14:01:00 -0400533 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400534 //last row peer composing view
535 result = [tableView makeViewWithIdentifier:@"PeerComposingMsgView" owner:conversationView];
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400536 result.alphaValue = 0;
537 [result animateCompozingIndicator: NO];
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400538 CGFloat alpha = peerComposingMessage ? 1 : 0;
539 CGFloat height = peerComposingMessage ? HEIGHT_FOR_COMPOSING_INDICATOR : DEFAULT_ROW_HEIGHT;
540 [result updateHeightConstraints: height];
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400541 if (alpha == 1) {
542 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
543 if (peerComposingMessage) {
544 result.alphaValue = alpha;
545 [result animateCompozingIndicator: YES];
546 }
547 });
548 }
549 return result;
550 }
Anthony Léonard2382b562017-12-13 15:51:28 -0500551
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500552 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500553
Kateryna Kostiuk0c068552020-03-30 09:48:17 -0400554 if (it == conv->interactions.end()) {
555 return [[NSView alloc] init];
556 }
557
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400558 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500559 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
560
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500561 switch (interaction.type) {
562 case lrc::api::interaction::Type::TEXT:
563 if (isOutgoing) {
564 result = [tableView makeViewWithIdentifier:@"RightMessageView" owner:self];
565 } else {
566 result = [tableView makeViewWithIdentifier:@"LeftMessageView" owner:self];
567 }
568 break;
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400569 case lrc::api::interaction::Type::DATA_TRANSFER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400570 return [self configureViewforTransfer:interaction interactionID: it->first tableView:tableView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500571 break;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500572 case lrc::api::interaction::Type::CONTACT:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400573 case lrc::api::interaction::Type::CALL: {
574 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
575 NSString* timeString = [self timeForMessage: msgTime];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400576 return [self makeGenericInteractionViewForTableView:tableView withText:interaction.body.toNSString() andTime:timeString];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400577 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500578 default: // If interaction is not of a known type
579 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400580 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400581 MessageSequencing sequence = [self computeSequencingFor:row];
582 BubbleType type = SINGLE;
583 if (sequence == FIRST_WITHOUT_TIME || sequence == FIRST_WITH_TIME) {
584 type = FIRST;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400585 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400586 if (sequence == MIDDLE_IN_SEQUENCE) {
587 type = MIDDLE;
588 }
589 if (sequence == LAST_IN_SEQUENCE) {
590 type = LAST;
591 }
592 result.msgBackground.type = type;
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400593 bool sendingFail = false;
594 [result.messageStatus setHidden:YES];
595 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
596 if (interaction.status == lrc::api::interaction::Status::SENDING) {
597 [result.messageStatus setHidden:NO];
598 [result.sendingMessageIndicator startAnimation:nil];
599 [result.messageFailed setHidden:YES];
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400600 } else if (interaction.status == lrc::api::interaction::Status::FAILURE) {
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400601 [result.messageStatus setHidden:NO];
602 [result.sendingMessageIndicator setHidden:YES];
603 [result.messageFailed setHidden:NO];
604 sendingFail = true;
605 }
606 }
607 [result setupForInteraction:it->first isFailed: sendingFail];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400608 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400609 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400610 [result.msgBackground setNeedsDisplay:YES];
611 [result setNeedsDisplay:YES];
612 [result.timeBox setNeedsDisplay:YES];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400613
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400614 NSString *text = interaction.body.toNSString();
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400615 text = [text removeEmptyLinesAtBorders];
616
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400617 NSMutableAttributedString* msgAttString =
Kateryna Kostiuk5a41a8d2020-05-13 09:05:56 -0400618 [[NSMutableAttributedString alloc] initWithString:text
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400619 attributes:[self messageAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400620
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400621 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400622
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400623 [result updateMessageConstraint:messageSize.width andHeight:messageSize.height timeIsVisible:shouldDisplayTime isTopPadding: shouldApplyPadding];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400624 [[result.msgView textStorage] appendAttributedString:msgAttString];
Anthony Léonard2382b562017-12-13 15:51:28 -0500625
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400626 NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
627 NSArray *matches = [linkDetector matchesInString:result.msgView.string options:0 range:NSMakeRange(0, result.msgView.string.length)];
628
629 [result.msgView.textStorage beginEditing];
630
631 for (NSTextCheckingResult *match in matches) {
632 if (!match.URL) continue;
633
634 NSDictionary *linkAttributes = @{
635 NSLinkAttributeName: match.URL,
636 };
637 [result.msgView.textStorage addAttributes:linkAttributes range:match.range];
638 }
639
640 [result.msgView.textStorage endEditing];
641
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400642 if (shouldDisplayTime) {
643 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
644 NSString* timeString = [self timeForMessage: msgTime];
645 result.timeLabel.stringValue = timeString;
Anthony Léonard64e19672018-01-18 16:40:34 -0500646 }
647
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400648 bool shouldDisplayAvatar = (sequence != MIDDLE_IN_SEQUENCE && sequence != FIRST_WITHOUT_TIME
649 && sequence != FIRST_WITH_TIME) ? YES : NO;
650 [result.photoView setHidden:!shouldDisplayAvatar];
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400651 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
652 auto image = QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)));
653 BOOL showIndicator = convModel_->isLastDisplayed(convUid_, it->first, conv->participants.front());
654 [result.readIndicator setHidden: !showIndicator];
655 [result.readIndicator setImage:image];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400656 if (!isOutgoing && shouldDisplayAvatar) {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400657 [result.photoView setImage:image];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400658 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400659 return result;
660}
661
Anthony Léonard2382b562017-12-13 15:51:28 -0500662- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400663{
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400664 try {
665 double someWidth = tableView.frame.size.width * 0.7;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400666
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400667 auto* conv = [self getCurrentConversation];
Anthony Léonard2382b562017-12-13 15:51:28 -0500668
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400669 if (conv == nil)
670 return HEIGHT_DEFAULT;
Kateryna Kostiuk0c068552020-03-30 09:48:17 -0400671
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400672 auto size = [conversationView numberOfRows] - 1;
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400673
674 if (row > size || row > conv->interactions.size()) {
675 return HEIGHT_DEFAULT;
676 }
677 if (row == size) {
678 return peerComposingMessage ? HEIGHT_FOR_COMPOSING_INDICATOR : DEFAULT_ROW_HEIGHT;
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400679 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400680
681 auto it = conv->interactions.begin();
682
683 std::advance(it, row);
684
685 if (it == conv->interactions.end()) {
686 return HEIGHT_DEFAULT;
687 }
688
689 auto interaction = it->second;
690
691 MessageSequencing sequence = [self computeSequencingFor:row];
692
693 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
694
695 if(interaction.type == lrc::api::interaction::Type::DATA_TRANSFER) {
696 if( interaction.status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
697 NSString* name = interaction.body.toNSString();
698 NSImage* image = [self getImageForFilePath:name];
699 if (([name rangeOfString:@"/"].location == NSNotFound)) {
700 image = [self getImageForFilePath:[self getDataTransferPath:it->first]];
701 }
702 if (image != nil) {
703 CGFloat widthScaleFactor = MAX_TRANSFERED_IMAGE_SIZE / image.size.width;
704 CGFloat heightScaleFactor = MAX_TRANSFERED_IMAGE_SIZE / image.size.height;
705 CGFloat heigt = 0;
706 if((widthScaleFactor >= 1) && (heightScaleFactor >= 1)) {
707 heigt = image.size.height;
708 } else {
709 CGFloat scale = MIN(widthScaleFactor, heightScaleFactor);
710 heigt = image.size.height * scale;
711 }
712 return heigt + TIME_BOX_HEIGHT;
713 }
714 }
715 return BUBBLE_HEIGHT_FOR_TRANSFERED_FILE + TIME_BOX_HEIGHT;
716 }
717
718 if(interaction.type == lrc::api::interaction::Type::CONTACT || interaction.type == lrc::api::interaction::Type::CALL)
719 return GENERIC_CELL_HEIGHT;
720
721 NSString *text = interaction.body.toNSString();
722 text = [text removeEmptyLinesAtBorders];
723
724 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
725 CGFloat singleLignMessageHeight = 15;
726
727 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
728
729 if (shouldDisplayTime) {
730 return MAX(messageSize.height + TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2,
731 TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2 + singleLignMessageHeight);
732 }
733 if(shouldApplyPadding) {
734 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2 + 15,
735 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2 + 15);
736 }
737 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2,
738 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2);
739 } catch (std::out_of_range& e) {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400740 return DEFAULT_ROW_HEIGHT;
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400741 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400742}
743
744#pragma mark - message view parameters
745
746-(NSString *) getDataTransferPath:(uint64_t)interactionId {
747 lrc::api::datatransfer::Info info = {};
748 convModel_->getTransferInfo(interactionId, info);
749 double convertData = static_cast<double>(info.totalSize);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400750 return info.path.toNSString();
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400751}
752
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400753-(NSImage*) getImageForFilePath: (NSString *) path {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400754 if (path.length <= 0) {return nil;}
755 if (![[NSFileManager defaultManager] fileExistsAtPath: path]) {return nil;}
756 NSImage* transferedImage = [[NSImage alloc] initWithContentsOfFile: path];
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400757 return transferedImage;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400758}
759
760-(CGSize) sizeFor:(NSString *) message maxWidth:(CGFloat) width {
761 CGFloat horizaontalMargin = 6;
Anthony Léonard2382b562017-12-13 15:51:28 -0500762 NSMutableAttributedString* msgAttString =
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400763 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@", message]
Anthony Léonard2382b562017-12-13 15:51:28 -0500764 attributes:[self messageAttributes]];
765
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400766 CGFloat finalWidth = MIN(msgAttString.size.width + horizaontalMargin * 2, width);
767 NSRect frame = NSMakeRect(0, 0, finalWidth, msgAttString.size.height);
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400768 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400769 [[tv textStorage] setAttributedString:msgAttString];
770 [tv sizeToFit];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400771 return tv.frame.size;
772}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400773
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400774-(MessageSequencing) computeSequencingFor:(NSInteger) row {
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400775 try {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400776 auto* conv = [self getCurrentConversation];
777 if (row >= conversationView.numberOfRows - 1 || row >= conv->interactions.size()) {
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400778 return SINGLE_WITHOUT_TIME;
779 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400780 if (conv == nil)
781 return SINGLE_WITHOUT_TIME;
782 auto it = conv->interactions.begin();
783 std::advance(it, row);
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400784 if (it == conv->interactions.end()) {
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400785 return SINGLE_WITHOUT_TIME;
786 }
787 auto interaction = it->second;
788 if (interaction.type != lrc::api::interaction::Type::TEXT) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400789 return SINGLE_WITH_TIME;
790 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400791 // first message in comversation
792 if (row == 0) {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400793 if (it == conv->interactions.end() || conv->interactions.size() < 2) {
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400794 return SINGLE_WITH_TIME;
795 }
796 auto nextIt = it;
797 nextIt++;
798 if (nextIt == conv->interactions.end()) {
799 return SINGLE_WITH_TIME;
800 }
801 auto nextInteraction = nextIt->second;
802 if ([self sequenceChangedFrom:interaction to: nextInteraction]) {
803 return SINGLE_WITH_TIME;
804 }
805 return FIRST_WITH_TIME;
806 }
807 // last message in comversation
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400808 if (row == conv->interactions.size() - 1) {
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400809 if(it == conv->interactions.begin()) {
810 return SINGLE_WITH_TIME;
811 }
812 auto previousIt = it;
813 previousIt--;
814 auto previousInteraction = previousIt->second;
815 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
816 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
817 if (!timeChanged && !authorChanged) {
818 return LAST_IN_SEQUENCE;
819 }
820 if (!timeChanged && authorChanged) {
821 return SINGLE_WITHOUT_TIME;
822 }
Kateryna Kostiukcd9397f2020-04-13 09:40:53 -0400823 return SINGLE_WITH_TIME;
824 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400825 // single message in comversation
826 if(it == conv->interactions.begin() || it == conv->interactions.end()) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400827 return SINGLE_WITH_TIME;
828 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400829 // message in the middle of conversation
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400830 auto previousIt = it;
831 previousIt--;
832 auto previousInteraction = previousIt->second;
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400833 auto nextIt = it;
834 nextIt++;
835 if (nextIt == conv->interactions.end()) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400836 return SINGLE_WITHOUT_TIME;
837 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400838 auto nextInteraction = nextIt->second;
839
840 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
841 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
842 bool sequenceWillChange = [self sequenceChangedFrom:interaction to: nextInteraction];
843 if (previousInteraction.type == lrc::api::interaction::Type::DATA_TRANSFER) {
844 if(!sequenceWillChange) {
845 return FIRST_WITH_TIME;
846 }
847 return SINGLE_WITH_TIME;
848 }
849 if (!sequenceWillChange) {
850 if (!timeChanged && !authorChanged) {
851 return MIDDLE_IN_SEQUENCE;
852 }
853 if (timeChanged) {
854 return FIRST_WITH_TIME;
855 }
856 return FIRST_WITHOUT_TIME;
857 } if (!timeChanged && !authorChanged) {
858 return LAST_IN_SEQUENCE;
859 } if (timeChanged) {
860 return SINGLE_WITH_TIME;
861 }
862 return SINGLE_WITHOUT_TIME;
863 } catch (std::out_of_range& e) {
Kateryna Kostiukcd9397f2020-04-13 09:40:53 -0400864 return SINGLE_WITHOUT_TIME;
865 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400866}
867
868-(bool) sequenceChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
869 return ([self sequenceTimeChangedFrom:firstInteraction to:secondInteraction] || [self sequenceAuthorChangedFrom:firstInteraction to:secondInteraction]);
870}
871
872-(bool) sequenceTimeChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
873 bool timeChanged = NO;
874 NSDate* firstMessageTime = [NSDate dateWithTimeIntervalSince1970:firstInteraction.timestamp];
875 NSDate* secondMessageTime = [NSDate dateWithTimeIntervalSince1970:secondInteraction.timestamp];
876 bool hourComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitHour];
877 bool minutComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitMinute];
878 if(hourComp != NSOrderedSame || minutComp != NSOrderedSame) {
879 timeChanged = YES;
880 }
881 return timeChanged;
882}
883
884-(bool) sequenceAuthorChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
885 bool authorChanged = YES;
886 bool isOutgoing = lrc::api::interaction::isOutgoing(firstInteraction);
887 if ((secondInteraction.type == lrc::api::interaction::Type::TEXT) && (isOutgoing == lrc::api::interaction::isOutgoing(secondInteraction))) {
888 authorChanged = NO;
889 }
890 return authorChanged;
891}
892
893-(NSString *)timeForMessage:(NSDate*) msgTime {
894 NSDate *today = [NSDate date];
895 NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
Kateryna Kostiukaf6d5e22018-06-12 15:00:00 -0400896 [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:[[NSLocale currentLocale] localeIdentifier]]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400897 if ([[NSCalendar currentCalendar] compareDate:today
898 toDate:msgTime
899 toUnitGranularity:NSCalendarUnitYear]!= NSOrderedSame) {
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500900 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterLongStyle timeStyle:NSDateFormatterMediumStyle];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400901 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400902 if ([[NSCalendar currentCalendar] compareDate:today
903 toDate:msgTime
904 toUnitGranularity:NSCalendarUnitDay]!= NSOrderedSame ||
905 [[NSCalendar currentCalendar] compareDate:today
906 toDate:msgTime
907 toUnitGranularity:NSCalendarUnitMonth]!= NSOrderedSame) {
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500908 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterShortStyle];
909 }
910 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterShortStyle];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400911}
912
Kateryna Kostiukf6317422018-09-27 17:08:20 -0400913- (void) updateSendMessageHeight {
914 NSAttributedString *msgAttString = messageField.attributedStringValue;
915 NSRect frame = NSMakeRect(0, 0, messageField.frame.size.width, msgAttString.size.height);
916 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
917 [[tv textStorage] setAttributedString:msgAttString];
918 [tv sizeToFit];
919 CGFloat height = tv.frame.size.height + MEESAGE_MARGIN * 2;
920 CGFloat newHeight = MIN(SEND_PANEL_MAX_HEIGHT, MAX(SEND_PANEL_DEFAULT_HEIGHT, height));
921 if(messagesBottomMargin.constant == newHeight) {
922 return;
923 }
924 messagesBottomMargin.constant = newHeight;
925 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
926 [self scrollToBottom];
927 sendPanelHeight.constant = newHeight;
928 });
929}
930
Anthony Léonard2382b562017-12-13 15:51:28 -0500931#pragma mark - NSTableViewDataSource
932
933- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
934{
935 auto* conv = [self getCurrentConversation];
936
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400937 // return conversation +1 view for composing indicator
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500938 if (conv)
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400939 return conv->interactions.size() + 1;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500940 else
941 return 0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500942}
943
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400944#pragma mark - Text formatting
945
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400946- (NSMutableDictionary*) messageAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400947{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400948 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400949 attrs[NSForegroundColorAttributeName] = [NSColor labelColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400950 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400951 return attrs;
952}
953
954- (NSParagraphStyle*) paragraphStyle
955{
956 /*
957 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
958 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
959 to copy, we use the default one.
960
961 The default values supplied by the default NSParagraphStyle are:
962 Alignment NSNaturalTextAlignment
963 Tab stops 12 left-aligned tabs, spaced by 28.0 points
964 Line break mode NSLineBreakByWordWrapping
965 All others 0.0
966 */
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400967 NSMutableParagraphStyle* aMutableParagraphStyle =
968 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
969 [aMutableParagraphStyle setHeadIndent:1.0];
970 [aMutableParagraphStyle setFirstLineHeadIndent:1.0];
971 return aMutableParagraphStyle;
972}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400973
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500974#pragma mark - Actions
975
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400976- (void)acceptIncomingFile:(id)sender {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400977 auto interId = [(IMTableCellView*)[[[[[[sender superview] superview] superview] superview] superview] superview] interaction];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500978 auto& inter = [self getCurrentConversation]->interactions.find(interId)->second;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400979 if (convModel_ && !convUid_.isEmpty()) {
Kateryna Kostiuk26405ac2020-05-27 14:25:13 -0400980 convModel_->acceptTransfer(convUid_, interId);
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500981 }
982}
983
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400984- (void)declineIncomingFile:(id)sender {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400985 auto inter = [(IMTableCellView*)[[[[[[sender superview] superview] superview] superview] superview] superview] interaction];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400986 if (convModel_ && !convUid_.isEmpty()) {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500987 convModel_->cancelTransfer(convUid_, inter);
988 }
989}
990
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400991- (void)imagePreview:(id)sender {
992 uint64_t interId;
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400993 if ([[[[[[sender superview] superview] superview] superview] superview] isKindOfClass:[IMTableCellView class]]) {
994 interId = [(IMTableCellView*)[[[[[sender superview] superview] superview] superview] superview] interaction];
995 } else if ([[[[[sender superview] superview] superview] superview] isKindOfClass:[IMTableCellView class]]) {
996 interId = [(IMTableCellView*)[[[[sender superview] superview] superview] superview] interaction];
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400997 } else {
998 return;
999 }
1000 auto it = [self getCurrentConversation]->interactions.find(interId);
1001 if (it == [self getCurrentConversation]->interactions.end()) {
1002 return;
1003 }
1004 auto& interaction = it->second;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001005 NSString* name = interaction.body.toNSString();
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001006 if (([name rangeOfString:@"/"].location == NSNotFound)) {
1007 name = [self getDataTransferPath:interId];
1008 }
1009 previewImage = name;
Kateryna Kostiuk30c6ac22020-05-06 17:42:59 -04001010 if (!previewImage || previewImage.length <= 0) {
1011 return;
1012 }
Kateryna Kostiuka116a0a2020-07-09 11:39:22 -04001013 [self addToResponderChain];
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001014 if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) {
1015 [[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
1016 } else {
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001017 dispatch_async(dispatch_get_main_queue(), ^{
1018 [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:self];
1019 });
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001020 }
1021}
1022
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001023- (IBAction)sendMessage:(id)sender {
1024 NSString* text = self.message;
Kateryna Kostiuka8b6b562019-02-01 13:26:18 -05001025 unichar separatorChar = NSLineSeparatorCharacter;
1026 NSString *separatorString = [NSString stringWithCharacters:&separatorChar length:1];
1027 text = [text stringByReplacingOccurrencesOfString: separatorString withString: @"\n"];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001028 if (text && text.length > 0) {
1029 auto* conv = [self getCurrentConversation];
Kateryna Kostiuk0c068552020-03-30 09:48:17 -04001030 if (conv == nil)
1031 return;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001032 convModel_->sendMessage(convUid_, QString::fromNSString(text));
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001033 self.message = @"";
1034 if(sendPanelHeight.constant != SEND_PANEL_DEFAULT_HEIGHT) {
1035 sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
1036 messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
1037 [self scrollToBottom];
1038 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001039 if (composingMessage) {
1040 composingMessage = false;
1041 convModel_->setIsComposing(convUid_, composingMessage);
1042 }
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001043 }
1044}
1045
1046- (IBAction)openEmojy:(id)sender {
1047 [messageField.window makeFirstResponder: messageField];
1048 [[messageField currentEditor] moveToEndOfLine:nil];
1049 [NSApp orderFrontCharacterPalette: messageField];
1050}
1051
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001052- (IBAction)startVideoMessage:(id)sender
1053{
1054 [self startRecording:NO];
1055}
1056
1057- (IBAction)startAudioMessage:(id)sender
1058{
1059 [self startRecording:YES];
1060}
1061-(void) startRecording:(BOOL)isAudio {
1062 if (recordingController == nil) {
1063 recordingController = [[RecordFileVC alloc] initWithNibName:@"RecordFileVC" bundle:nil avModel: self->avModel];
1064 recordingController.delegate = self;
1065 }
1066 if(recordMessagePopover != nil)
1067 {
1068 [self closeRecordingView];
1069 return;
1070 }
1071 recordMessagePopover = [[NSPopover alloc] init];
1072 [recordingController prepareRecordingView: isAudio];
1073 [recordMessagePopover setContentSize: recordingController.view.frame.size];
1074 [recordMessagePopover setContentViewController:recordingController];
1075 [recordMessagePopover setAnimates:YES];
1076 NSButton *anchorButton = isAudio ? recordAudioButton : recordVideoButton;
1077 [recordMessagePopover showRelativeToRect: anchorButton.bounds
1078 ofView: anchorButton
1079 preferredEdge: NSMaxYEdge];
1080}
1081
1082-(void) sendFile:(NSString *) name withFilePath:(NSString *) path {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001083 convModel_->sendFile(convUid_, QString::fromNSString(path), QString::fromNSString(name));
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001084}
1085
1086-(void) closeRecordingView {
1087 if(recordMessagePopover != nil) {
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001088 [recordMessagePopover close];
1089 recordMessagePopover = nil;
Kateryna Kostiuka7404812019-10-28 12:24:46 -04001090 recordingController.stopRecordingView;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001091 }
1092}
1093
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001094- (IBAction)sendFile:(id)sender {
1095 NSOpenPanel* filePicker = [NSOpenPanel openPanel];
1096 [filePicker setCanChooseFiles:YES];
1097 [filePicker setCanChooseDirectories:NO];
1098 [filePicker setAllowsMultipleSelection:NO];
1099
1100 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
1101 if ([[filePicker URLs] count] == 1) {
1102 NSURL* url = [[filePicker URLs] objectAtIndex:0];
1103 const char* fullPath = [url fileSystemRepresentation];
1104 NSString* fileName = [url lastPathComponent];
1105 if (convModel_) {
1106 auto* conv = [self getCurrentConversation];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001107 convModel_->sendFile(convUid_, QString::fromStdString(fullPath), QString::fromNSString(fileName));
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001108 }
1109 }
1110 }
1111}
1112
1113
1114#pragma mark - NSTextFieldDelegate
1115
1116- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
1117{
1118 if (commandSelector == @selector(insertNewline:)) {
1119 if(self.message.length > 0) {
1120 [self sendMessage: nil];
1121 } else if(messagesBottomMargin.constant != SEND_PANEL_DEFAULT_HEIGHT) {
1122 sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
1123 messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
1124 [self scrollToBottom];
1125 }
1126 return YES;
1127 }
1128 return NO;
1129}
1130
1131- (void)controlTextDidChange:(NSNotification *)aNotification {
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001132 [self checkIfcomposingMsg];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001133 [self updateSendMessageHeight];
1134}
1135
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001136- (void) checkIfcomposingMsg {
1137 [self updateSendMessageHeight];
1138 BOOL haveText = [messageField.stringValue removeEmptyLinesAtBorders].length != 0;
1139 if (haveText != composingMessage) {
1140 composingMessage = haveText;
1141 convModel_->setIsComposing(convUid_, composingMessage);
1142 }
1143}
1144
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001145#pragma mark - QLPreviewPanelDataSource
1146
1147-(void)beginPreviewPanelControl:(QLPreviewPanel *)panel
1148{
1149 panel.dataSource = self;
1150}
1151
1152- (void)endPreviewPanelControl:(QLPreviewPanel *)panel {
1153 panel.dataSource = nil;
Kateryna Kostiuka116a0a2020-07-09 11:39:22 -04001154 [self removeFromResponderChain];
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001155}
1156
1157-(BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel
1158{
1159 return YES;
1160}
1161
1162- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel {
1163 return 1;
1164}
1165
1166- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index {
Kateryna Kostiuka116a0a2020-07-09 11:39:22 -04001167 if (previewImage == nil) {
1168 return nil;
1169 }
Kateryna Kostiuk30c6ac22020-05-06 17:42:59 -04001170 try {
1171 return [NSURL fileURLWithPath: previewImage];
1172 } catch (NSException *exception) {
1173 nil;
1174 }
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001175}
1176
Kateryna Kostiuka116a0a2020-07-09 11:39:22 -04001177- (void)addToResponderChain {
1178 if (conversationView.window &&
1179 ![[conversationView.window nextResponder] isEqual:self]) {
1180 NSResponder * aNextResponder = [conversationView.window nextResponder];
1181 [conversationView.window setNextResponder:self];
1182 }
1183}
1184
1185
1186- (void)removeFromResponderChain {
1187 if (conversationView.window &&
1188 [[conversationView.window nextResponder] isEqual:self]) {
1189 NSResponder * aNextResponder = [conversationView.window nextResponder];
1190 [conversationView.window setNextResponder:[self nextResponder]];
1191 }
1192}
1193
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04001194@end