blob: 4e573195ff524a3d3c1e340f1cf67c5380fcd92d [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 Kostiuka7b909c2020-10-19 11:46:26 -0400180 auto convOpt = getConversationFromUid(convUid_, *convModel_);
181 if (convOpt.has_value()) {
kkostiukf81c6372021-01-11 18:51:28 -0500182 lrc::api::conversation::Info& conversation = *convOpt;
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400183 cachedConv_ = &conversation;
Kateryna Kostiuk3541ae22020-08-17 12:26:14 -0400184 }
Anthony Léonard2382b562017-12-13 15:51:28 -0500185 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_);
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400317 modelSortedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelChanged,
Anthony Léonard2382b562017-12-13 15:51:28 -0500318 [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 Kostiukd508f752020-11-13 16:26:00 -0500463 NSFont *nameFont = [NSFont systemFontOfSize: 12 weight: NSFontWeightLight];
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400464 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) {
Kateryna Kostiukdc720842020-09-10 16:44:02 -0400498 @autoreleasepool {
499 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
500 auto* conv = [self getCurrentConversation];
501 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
502 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400503 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500504 return result;
505}
506
Anthony Léonard2382b562017-12-13 15:51:28 -0500507#pragma mark - NSTableViewDelegate methods
508- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400509{
510 return YES;
511}
512
Anthony Léonard2382b562017-12-13 15:51:28 -0500513- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400514{
Anthony Léonard2382b562017-12-13 15:51:28 -0500515 return NO;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400516}
517
Anthony Léonard2382b562017-12-13 15:51:28 -0500518- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400519{
Anthony Léonard2382b562017-12-13 15:51:28 -0500520 auto* conv = [self getCurrentConversation];
Anthony Léonard2382b562017-12-13 15:51:28 -0500521 if (conv == nil)
522 return nil;
523
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400524 IMTableCellView* result;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500525 auto it = conv->interactions.begin();
Kateryna Kostiuka1201922020-04-20 11:59:35 -0400526 auto size = [conversationView numberOfRows] - 1;
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400527
528 if (row > size || row > conv->interactions.size()) {
Kateryna Kostiuk0c068552020-03-30 09:48:17 -0400529 return [[NSView alloc] init];
530 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400531
532 if (row == size) {
Kateryna Kostiukb39ca192020-03-27 14:01:00 -0400533 if (size < 1) {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400534 return nil;
Kateryna Kostiukb39ca192020-03-27 14:01:00 -0400535 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400536 //last row peer composing view
537 result = [tableView makeViewWithIdentifier:@"PeerComposingMsgView" owner:conversationView];
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400538 result.alphaValue = 0;
539 [result animateCompozingIndicator: NO];
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400540 CGFloat alpha = peerComposingMessage ? 1 : 0;
541 CGFloat height = peerComposingMessage ? HEIGHT_FOR_COMPOSING_INDICATOR : DEFAULT_ROW_HEIGHT;
542 [result updateHeightConstraints: height];
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400543 if (alpha == 1) {
544 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
545 if (peerComposingMessage) {
546 result.alphaValue = alpha;
547 [result animateCompozingIndicator: YES];
548 }
549 });
550 }
551 return result;
552 }
Anthony Léonard2382b562017-12-13 15:51:28 -0500553
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500554 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500555
Kateryna Kostiuk0c068552020-03-30 09:48:17 -0400556 if (it == conv->interactions.end()) {
557 return [[NSView alloc] init];
558 }
559
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400560 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500561 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
562
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500563 switch (interaction.type) {
564 case lrc::api::interaction::Type::TEXT:
565 if (isOutgoing) {
566 result = [tableView makeViewWithIdentifier:@"RightMessageView" owner:self];
567 } else {
568 result = [tableView makeViewWithIdentifier:@"LeftMessageView" owner:self];
569 }
570 break;
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400571 case lrc::api::interaction::Type::DATA_TRANSFER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400572 return [self configureViewforTransfer:interaction interactionID: it->first tableView:tableView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500573 break;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500574 case lrc::api::interaction::Type::CONTACT:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400575 case lrc::api::interaction::Type::CALL: {
576 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
577 NSString* timeString = [self timeForMessage: msgTime];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400578 return [self makeGenericInteractionViewForTableView:tableView withText:interaction.body.toNSString() andTime:timeString];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400579 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500580 default: // If interaction is not of a known type
581 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400582 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400583 MessageSequencing sequence = [self computeSequencingFor:row];
584 BubbleType type = SINGLE;
585 if (sequence == FIRST_WITHOUT_TIME || sequence == FIRST_WITH_TIME) {
586 type = FIRST;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400587 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400588 if (sequence == MIDDLE_IN_SEQUENCE) {
589 type = MIDDLE;
590 }
591 if (sequence == LAST_IN_SEQUENCE) {
592 type = LAST;
593 }
594 result.msgBackground.type = type;
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400595 bool sendingFail = false;
596 [result.messageStatus setHidden:YES];
597 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
598 if (interaction.status == lrc::api::interaction::Status::SENDING) {
599 [result.messageStatus setHidden:NO];
600 [result.sendingMessageIndicator startAnimation:nil];
601 [result.messageFailed setHidden:YES];
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400602 } else if (interaction.status == lrc::api::interaction::Status::FAILURE) {
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400603 [result.messageStatus setHidden:NO];
604 [result.sendingMessageIndicator setHidden:YES];
605 [result.messageFailed setHidden:NO];
606 sendingFail = true;
607 }
608 }
609 [result setupForInteraction:it->first isFailed: sendingFail];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400610 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400611 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400612 [result.msgBackground setNeedsDisplay:YES];
613 [result setNeedsDisplay:YES];
614 [result.timeBox setNeedsDisplay:YES];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400615
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400616 NSString *text = interaction.body.toNSString();
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400617 text = [text removeEmptyLinesAtBorders];
618
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400619 NSMutableAttributedString* msgAttString =
Kateryna Kostiuk5a41a8d2020-05-13 09:05:56 -0400620 [[NSMutableAttributedString alloc] initWithString:text
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400621 attributes:[self messageAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400622
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400623 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400624
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400625 [result updateMessageConstraint:messageSize.width andHeight:messageSize.height timeIsVisible:shouldDisplayTime isTopPadding: shouldApplyPadding];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400626 [[result.msgView textStorage] appendAttributedString:msgAttString];
Anthony Léonard2382b562017-12-13 15:51:28 -0500627
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400628 NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
629 NSArray *matches = [linkDetector matchesInString:result.msgView.string options:0 range:NSMakeRange(0, result.msgView.string.length)];
630
631 [result.msgView.textStorage beginEditing];
632
633 for (NSTextCheckingResult *match in matches) {
634 if (!match.URL) continue;
635
636 NSDictionary *linkAttributes = @{
637 NSLinkAttributeName: match.URL,
638 };
639 [result.msgView.textStorage addAttributes:linkAttributes range:match.range];
640 }
641
642 [result.msgView.textStorage endEditing];
643
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400644 if (shouldDisplayTime) {
645 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
646 NSString* timeString = [self timeForMessage: msgTime];
647 result.timeLabel.stringValue = timeString;
Anthony Léonard64e19672018-01-18 16:40:34 -0500648 }
649
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400650 bool shouldDisplayAvatar = (sequence != MIDDLE_IN_SEQUENCE && sequence != FIRST_WITHOUT_TIME
651 && sequence != FIRST_WITH_TIME) ? YES : NO;
652 [result.photoView setHidden:!shouldDisplayAvatar];
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400653 BOOL showIndicator = convModel_->isLastDisplayed(convUid_, it->first, conv->participants.front());
654 [result.readIndicator setHidden: !showIndicator];
Kateryna Kostiukdc720842020-09-10 16:44:02 -0400655 @autoreleasepool {
656 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
657 auto image = QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)));
658 [result.readIndicator setImage:image];
659 if (!isOutgoing && shouldDisplayAvatar) {
660 [result.photoView setImage:image];
661 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400662 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400663 return result;
664}
665
Anthony Léonard2382b562017-12-13 15:51:28 -0500666- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400667{
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400668 try {
669 double someWidth = tableView.frame.size.width * 0.7;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400670
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400671 auto* conv = [self getCurrentConversation];
Anthony Léonard2382b562017-12-13 15:51:28 -0500672
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400673 if (conv == nil)
674 return HEIGHT_DEFAULT;
Kateryna Kostiuk0c068552020-03-30 09:48:17 -0400675
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400676 auto size = [conversationView numberOfRows] - 1;
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400677
678 if (row > size || row > conv->interactions.size()) {
679 return HEIGHT_DEFAULT;
680 }
681 if (row == size) {
682 return peerComposingMessage ? HEIGHT_FOR_COMPOSING_INDICATOR : DEFAULT_ROW_HEIGHT;
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400683 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400684
685 auto it = conv->interactions.begin();
686
687 std::advance(it, row);
688
689 if (it == conv->interactions.end()) {
690 return HEIGHT_DEFAULT;
691 }
692
693 auto interaction = it->second;
694
695 MessageSequencing sequence = [self computeSequencingFor:row];
696
697 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
698
699 if(interaction.type == lrc::api::interaction::Type::DATA_TRANSFER) {
700 if( interaction.status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
701 NSString* name = interaction.body.toNSString();
702 NSImage* image = [self getImageForFilePath:name];
703 if (([name rangeOfString:@"/"].location == NSNotFound)) {
704 image = [self getImageForFilePath:[self getDataTransferPath:it->first]];
705 }
706 if (image != nil) {
707 CGFloat widthScaleFactor = MAX_TRANSFERED_IMAGE_SIZE / image.size.width;
708 CGFloat heightScaleFactor = MAX_TRANSFERED_IMAGE_SIZE / image.size.height;
709 CGFloat heigt = 0;
710 if((widthScaleFactor >= 1) && (heightScaleFactor >= 1)) {
711 heigt = image.size.height;
712 } else {
713 CGFloat scale = MIN(widthScaleFactor, heightScaleFactor);
714 heigt = image.size.height * scale;
715 }
716 return heigt + TIME_BOX_HEIGHT;
717 }
718 }
719 return BUBBLE_HEIGHT_FOR_TRANSFERED_FILE + TIME_BOX_HEIGHT;
720 }
721
722 if(interaction.type == lrc::api::interaction::Type::CONTACT || interaction.type == lrc::api::interaction::Type::CALL)
723 return GENERIC_CELL_HEIGHT;
724
725 NSString *text = interaction.body.toNSString();
726 text = [text removeEmptyLinesAtBorders];
727
728 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
729 CGFloat singleLignMessageHeight = 15;
730
731 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
732
733 if (shouldDisplayTime) {
734 return MAX(messageSize.height + TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2,
735 TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2 + singleLignMessageHeight);
736 }
737 if(shouldApplyPadding) {
738 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2 + 15,
739 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2 + 15);
740 }
741 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2,
742 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2);
743 } catch (std::out_of_range& e) {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400744 return DEFAULT_ROW_HEIGHT;
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400745 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400746}
747
748#pragma mark - message view parameters
749
750-(NSString *) getDataTransferPath:(uint64_t)interactionId {
751 lrc::api::datatransfer::Info info = {};
752 convModel_->getTransferInfo(interactionId, info);
753 double convertData = static_cast<double>(info.totalSize);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400754 return info.path.toNSString();
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400755}
756
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400757-(NSImage*) getImageForFilePath: (NSString *) path {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400758 if (path.length <= 0) {return nil;}
759 if (![[NSFileManager defaultManager] fileExistsAtPath: path]) {return nil;}
760 NSImage* transferedImage = [[NSImage alloc] initWithContentsOfFile: path];
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400761 return transferedImage;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400762}
763
764-(CGSize) sizeFor:(NSString *) message maxWidth:(CGFloat) width {
765 CGFloat horizaontalMargin = 6;
Anthony Léonard2382b562017-12-13 15:51:28 -0500766 NSMutableAttributedString* msgAttString =
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400767 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@", message]
Anthony Léonard2382b562017-12-13 15:51:28 -0500768 attributes:[self messageAttributes]];
769
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400770 CGFloat finalWidth = MIN(msgAttString.size.width + horizaontalMargin * 2, width);
771 NSRect frame = NSMakeRect(0, 0, finalWidth, msgAttString.size.height);
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400772 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400773 [[tv textStorage] setAttributedString:msgAttString];
774 [tv sizeToFit];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400775 return tv.frame.size;
776}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400777
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400778-(MessageSequencing) computeSequencingFor:(NSInteger) row {
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400779 try {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400780 auto* conv = [self getCurrentConversation];
781 if (row >= conversationView.numberOfRows - 1 || row >= conv->interactions.size()) {
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400782 return SINGLE_WITHOUT_TIME;
783 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400784 if (conv == nil)
785 return SINGLE_WITHOUT_TIME;
786 auto it = conv->interactions.begin();
787 std::advance(it, row);
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400788 if (it == conv->interactions.end()) {
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400789 return SINGLE_WITHOUT_TIME;
790 }
791 auto interaction = it->second;
792 if (interaction.type != lrc::api::interaction::Type::TEXT) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400793 return SINGLE_WITH_TIME;
794 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400795 // first message in comversation
796 if (row == 0) {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400797 if (it == conv->interactions.end() || conv->interactions.size() < 2) {
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400798 return SINGLE_WITH_TIME;
799 }
800 auto nextIt = it;
801 nextIt++;
802 if (nextIt == conv->interactions.end()) {
803 return SINGLE_WITH_TIME;
804 }
805 auto nextInteraction = nextIt->second;
806 if ([self sequenceChangedFrom:interaction to: nextInteraction]) {
807 return SINGLE_WITH_TIME;
808 }
809 return FIRST_WITH_TIME;
810 }
811 // last message in comversation
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400812 if (row == conv->interactions.size() - 1) {
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400813 if(it == conv->interactions.begin()) {
814 return SINGLE_WITH_TIME;
815 }
816 auto previousIt = it;
817 previousIt--;
818 auto previousInteraction = previousIt->second;
819 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
820 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
821 if (!timeChanged && !authorChanged) {
822 return LAST_IN_SEQUENCE;
823 }
824 if (!timeChanged && authorChanged) {
825 return SINGLE_WITHOUT_TIME;
826 }
Kateryna Kostiukcd9397f2020-04-13 09:40:53 -0400827 return SINGLE_WITH_TIME;
828 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400829 // single message in comversation
830 if(it == conv->interactions.begin() || it == conv->interactions.end()) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400831 return SINGLE_WITH_TIME;
832 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400833 // message in the middle of conversation
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400834 auto previousIt = it;
835 previousIt--;
836 auto previousInteraction = previousIt->second;
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400837 auto nextIt = it;
838 nextIt++;
839 if (nextIt == conv->interactions.end()) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400840 return SINGLE_WITHOUT_TIME;
841 }
Kateryna Kostiukbea05222020-05-29 11:20:33 -0400842 auto nextInteraction = nextIt->second;
843
844 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
845 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
846 bool sequenceWillChange = [self sequenceChangedFrom:interaction to: nextInteraction];
847 if (previousInteraction.type == lrc::api::interaction::Type::DATA_TRANSFER) {
848 if(!sequenceWillChange) {
849 return FIRST_WITH_TIME;
850 }
851 return SINGLE_WITH_TIME;
852 }
853 if (!sequenceWillChange) {
854 if (!timeChanged && !authorChanged) {
855 return MIDDLE_IN_SEQUENCE;
856 }
857 if (timeChanged) {
858 return FIRST_WITH_TIME;
859 }
860 return FIRST_WITHOUT_TIME;
861 } if (!timeChanged && !authorChanged) {
862 return LAST_IN_SEQUENCE;
863 } if (timeChanged) {
864 return SINGLE_WITH_TIME;
865 }
866 return SINGLE_WITHOUT_TIME;
867 } catch (std::out_of_range& e) {
Kateryna Kostiukcd9397f2020-04-13 09:40:53 -0400868 return SINGLE_WITHOUT_TIME;
869 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400870}
871
872-(bool) sequenceChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
873 return ([self sequenceTimeChangedFrom:firstInteraction to:secondInteraction] || [self sequenceAuthorChangedFrom:firstInteraction to:secondInteraction]);
874}
875
876-(bool) sequenceTimeChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
877 bool timeChanged = NO;
878 NSDate* firstMessageTime = [NSDate dateWithTimeIntervalSince1970:firstInteraction.timestamp];
879 NSDate* secondMessageTime = [NSDate dateWithTimeIntervalSince1970:secondInteraction.timestamp];
880 bool hourComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitHour];
881 bool minutComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitMinute];
882 if(hourComp != NSOrderedSame || minutComp != NSOrderedSame) {
883 timeChanged = YES;
884 }
885 return timeChanged;
886}
887
888-(bool) sequenceAuthorChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
889 bool authorChanged = YES;
890 bool isOutgoing = lrc::api::interaction::isOutgoing(firstInteraction);
891 if ((secondInteraction.type == lrc::api::interaction::Type::TEXT) && (isOutgoing == lrc::api::interaction::isOutgoing(secondInteraction))) {
892 authorChanged = NO;
893 }
894 return authorChanged;
895}
896
897-(NSString *)timeForMessage:(NSDate*) msgTime {
898 NSDate *today = [NSDate date];
899 NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
Kateryna Kostiukaf6d5e22018-06-12 15:00:00 -0400900 [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:[[NSLocale currentLocale] localeIdentifier]]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400901 if ([[NSCalendar currentCalendar] compareDate:today
902 toDate:msgTime
903 toUnitGranularity:NSCalendarUnitYear]!= NSOrderedSame) {
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500904 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterLongStyle timeStyle:NSDateFormatterMediumStyle];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400905 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400906 if ([[NSCalendar currentCalendar] compareDate:today
907 toDate:msgTime
908 toUnitGranularity:NSCalendarUnitDay]!= NSOrderedSame ||
909 [[NSCalendar currentCalendar] compareDate:today
910 toDate:msgTime
911 toUnitGranularity:NSCalendarUnitMonth]!= NSOrderedSame) {
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500912 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterShortStyle];
913 }
914 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterShortStyle];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400915}
916
Kateryna Kostiukf6317422018-09-27 17:08:20 -0400917- (void) updateSendMessageHeight {
918 NSAttributedString *msgAttString = messageField.attributedStringValue;
919 NSRect frame = NSMakeRect(0, 0, messageField.frame.size.width, msgAttString.size.height);
920 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
921 [[tv textStorage] setAttributedString:msgAttString];
922 [tv sizeToFit];
923 CGFloat height = tv.frame.size.height + MEESAGE_MARGIN * 2;
924 CGFloat newHeight = MIN(SEND_PANEL_MAX_HEIGHT, MAX(SEND_PANEL_DEFAULT_HEIGHT, height));
925 if(messagesBottomMargin.constant == newHeight) {
926 return;
927 }
928 messagesBottomMargin.constant = newHeight;
929 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
930 [self scrollToBottom];
931 sendPanelHeight.constant = newHeight;
932 });
933}
934
Anthony Léonard2382b562017-12-13 15:51:28 -0500935#pragma mark - NSTableViewDataSource
936
937- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
938{
939 auto* conv = [self getCurrentConversation];
940
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400941 // return conversation +1 view for composing indicator
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500942 if (conv)
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400943 return conv->interactions.size() + 1;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500944 else
945 return 0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500946}
947
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400948#pragma mark - Text formatting
949
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400950- (NSMutableDictionary*) messageAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400951{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400952 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiukd508f752020-11-13 16:26:00 -0500953 NSFont *font = [NSFont systemFontOfSize: 12 weight: NSFontWeightLight];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400954 attrs[NSForegroundColorAttributeName] = [NSColor labelColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400955 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
Kateryna Kostiukd508f752020-11-13 16:26:00 -0500956 attrs[NSFontAttributeName] = font;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400957 return attrs;
958}
959
960- (NSParagraphStyle*) paragraphStyle
961{
962 /*
963 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
964 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
965 to copy, we use the default one.
966
967 The default values supplied by the default NSParagraphStyle are:
968 Alignment NSNaturalTextAlignment
969 Tab stops 12 left-aligned tabs, spaced by 28.0 points
970 Line break mode NSLineBreakByWordWrapping
971 All others 0.0
972 */
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400973 NSMutableParagraphStyle* aMutableParagraphStyle =
974 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
975 [aMutableParagraphStyle setHeadIndent:1.0];
976 [aMutableParagraphStyle setFirstLineHeadIndent:1.0];
977 return aMutableParagraphStyle;
978}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400979
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500980#pragma mark - Actions
981
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400982- (void)acceptIncomingFile:(id)sender {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400983 auto interId = [(IMTableCellView*)[[[[[[sender superview] superview] superview] superview] superview] superview] interaction];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500984 auto& inter = [self getCurrentConversation]->interactions.find(interId)->second;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400985 if (convModel_ && !convUid_.isEmpty()) {
Kateryna Kostiuk26405ac2020-05-27 14:25:13 -0400986 convModel_->acceptTransfer(convUid_, interId);
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500987 }
988}
989
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400990- (void)declineIncomingFile:(id)sender {
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400991 auto inter = [(IMTableCellView*)[[[[[[sender superview] superview] superview] superview] superview] superview] interaction];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400992 if (convModel_ && !convUid_.isEmpty()) {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500993 convModel_->cancelTransfer(convUid_, inter);
994 }
995}
996
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400997- (void)imagePreview:(id)sender {
998 uint64_t interId;
Kateryna Kostiuk456cd812020-06-05 15:10:01 -0400999 if ([[[[[[sender superview] superview] superview] superview] superview] isKindOfClass:[IMTableCellView class]]) {
1000 interId = [(IMTableCellView*)[[[[[sender superview] superview] superview] superview] superview] interaction];
1001 } else if ([[[[[sender superview] superview] superview] superview] isKindOfClass:[IMTableCellView class]]) {
1002 interId = [(IMTableCellView*)[[[[sender superview] superview] superview] superview] interaction];
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001003 } else {
1004 return;
1005 }
1006 auto it = [self getCurrentConversation]->interactions.find(interId);
1007 if (it == [self getCurrentConversation]->interactions.end()) {
1008 return;
1009 }
1010 auto& interaction = it->second;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001011 NSString* name = interaction.body.toNSString();
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001012 if (([name rangeOfString:@"/"].location == NSNotFound)) {
1013 name = [self getDataTransferPath:interId];
1014 }
1015 previewImage = name;
Kateryna Kostiuk30c6ac22020-05-06 17:42:59 -04001016 if (!previewImage || previewImage.length <= 0) {
1017 return;
1018 }
Kateryna Kostiuka116a0a2020-07-09 11:39:22 -04001019 [self addToResponderChain];
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001020 if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) {
1021 [[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
1022 } else {
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001023 dispatch_async(dispatch_get_main_queue(), ^{
1024 [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:self];
1025 });
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001026 }
1027}
1028
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001029- (IBAction)sendMessage:(id)sender {
1030 NSString* text = self.message;
Kateryna Kostiuka8b6b562019-02-01 13:26:18 -05001031 unichar separatorChar = NSLineSeparatorCharacter;
1032 NSString *separatorString = [NSString stringWithCharacters:&separatorChar length:1];
1033 text = [text stringByReplacingOccurrencesOfString: separatorString withString: @"\n"];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001034 if (text && text.length > 0) {
1035 auto* conv = [self getCurrentConversation];
Kateryna Kostiuk0c068552020-03-30 09:48:17 -04001036 if (conv == nil)
1037 return;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001038 convModel_->sendMessage(convUid_, QString::fromNSString(text));
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001039 self.message = @"";
1040 if(sendPanelHeight.constant != SEND_PANEL_DEFAULT_HEIGHT) {
1041 sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
1042 messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
1043 [self scrollToBottom];
1044 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001045 if (composingMessage) {
1046 composingMessage = false;
1047 convModel_->setIsComposing(convUid_, composingMessage);
1048 }
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001049 }
1050}
1051
1052- (IBAction)openEmojy:(id)sender {
1053 [messageField.window makeFirstResponder: messageField];
1054 [[messageField currentEditor] moveToEndOfLine:nil];
1055 [NSApp orderFrontCharacterPalette: messageField];
1056}
1057
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001058- (IBAction)startVideoMessage:(id)sender
1059{
1060 [self startRecording:NO];
1061}
1062
1063- (IBAction)startAudioMessage:(id)sender
1064{
1065 [self startRecording:YES];
1066}
1067-(void) startRecording:(BOOL)isAudio {
1068 if (recordingController == nil) {
1069 recordingController = [[RecordFileVC alloc] initWithNibName:@"RecordFileVC" bundle:nil avModel: self->avModel];
1070 recordingController.delegate = self;
1071 }
1072 if(recordMessagePopover != nil)
1073 {
1074 [self closeRecordingView];
1075 return;
1076 }
1077 recordMessagePopover = [[NSPopover alloc] init];
1078 [recordingController prepareRecordingView: isAudio];
1079 [recordMessagePopover setContentSize: recordingController.view.frame.size];
1080 [recordMessagePopover setContentViewController:recordingController];
1081 [recordMessagePopover setAnimates:YES];
1082 NSButton *anchorButton = isAudio ? recordAudioButton : recordVideoButton;
1083 [recordMessagePopover showRelativeToRect: anchorButton.bounds
1084 ofView: anchorButton
1085 preferredEdge: NSMaxYEdge];
1086}
1087
1088-(void) sendFile:(NSString *) name withFilePath:(NSString *) path {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001089 convModel_->sendFile(convUid_, QString::fromNSString(path), QString::fromNSString(name));
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001090}
1091
1092-(void) closeRecordingView {
1093 if(recordMessagePopover != nil) {
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001094 [recordMessagePopover close];
1095 recordMessagePopover = nil;
Kateryna Kostiuka7404812019-10-28 12:24:46 -04001096 recordingController.stopRecordingView;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001097 }
1098}
1099
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001100- (IBAction)sendFile:(id)sender {
1101 NSOpenPanel* filePicker = [NSOpenPanel openPanel];
1102 [filePicker setCanChooseFiles:YES];
1103 [filePicker setCanChooseDirectories:NO];
1104 [filePicker setAllowsMultipleSelection:NO];
1105
1106 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
1107 if ([[filePicker URLs] count] == 1) {
1108 NSURL* url = [[filePicker URLs] objectAtIndex:0];
1109 const char* fullPath = [url fileSystemRepresentation];
1110 NSString* fileName = [url lastPathComponent];
1111 if (convModel_) {
1112 auto* conv = [self getCurrentConversation];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001113 convModel_->sendFile(convUid_, QString::fromStdString(fullPath), QString::fromNSString(fileName));
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001114 }
1115 }
1116 }
1117}
1118
1119
1120#pragma mark - NSTextFieldDelegate
1121
1122- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
1123{
1124 if (commandSelector == @selector(insertNewline:)) {
1125 if(self.message.length > 0) {
1126 [self sendMessage: nil];
1127 } else if(messagesBottomMargin.constant != SEND_PANEL_DEFAULT_HEIGHT) {
1128 sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
1129 messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
1130 [self scrollToBottom];
1131 }
1132 return YES;
1133 }
1134 return NO;
1135}
1136
1137- (void)controlTextDidChange:(NSNotification *)aNotification {
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001138 [self checkIfcomposingMsg];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001139 [self updateSendMessageHeight];
1140}
1141
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001142- (void) checkIfcomposingMsg {
1143 [self updateSendMessageHeight];
1144 BOOL haveText = [messageField.stringValue removeEmptyLinesAtBorders].length != 0;
1145 if (haveText != composingMessage) {
1146 composingMessage = haveText;
1147 convModel_->setIsComposing(convUid_, composingMessage);
1148 }
1149}
1150
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001151#pragma mark - QLPreviewPanelDataSource
1152
1153-(void)beginPreviewPanelControl:(QLPreviewPanel *)panel
1154{
1155 panel.dataSource = self;
1156}
1157
1158- (void)endPreviewPanelControl:(QLPreviewPanel *)panel {
1159 panel.dataSource = nil;
Kateryna Kostiuka116a0a2020-07-09 11:39:22 -04001160 [self removeFromResponderChain];
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001161}
1162
1163-(BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel
1164{
1165 return YES;
1166}
1167
1168- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel {
1169 return 1;
1170}
1171
1172- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index {
Kateryna Kostiuka116a0a2020-07-09 11:39:22 -04001173 if (previewImage == nil) {
1174 return nil;
1175 }
Kateryna Kostiuk30c6ac22020-05-06 17:42:59 -04001176 try {
1177 return [NSURL fileURLWithPath: previewImage];
1178 } catch (NSException *exception) {
1179 nil;
1180 }
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001181}
1182
Kateryna Kostiuka116a0a2020-07-09 11:39:22 -04001183- (void)addToResponderChain {
1184 if (conversationView.window &&
1185 ![[conversationView.window nextResponder] isEqual:self]) {
1186 NSResponder * aNextResponder = [conversationView.window nextResponder];
1187 [conversationView.window setNextResponder:self];
1188 }
1189}
1190
1191
1192- (void)removeFromResponderChain {
1193 if (conversationView.window &&
1194 [[conversationView.window nextResponder] isEqual:self]) {
1195 NSResponder * aNextResponder = [conversationView.window nextResponder];
1196 [conversationView.window setNextResponder:[self nextResponder]];
1197 }
1198}
1199
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04001200@end