blob: 20f445c7051f1822272342e834b5e954a3a3c046 [file] [log] [blame]
Andreas Traczyk252a94a2018-04-20 16:36:20 -04001
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04002/*
Sébastien Blin029ffa82019-01-02 17:43:48 -05003 * Copyright (C) 2015-2019 Savoir-faire Linux Inc.
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04004 * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
Anthony Léonard2382b562017-12-13 15:51:28 -05005 * Anthony Léonard <anthony.leonard@savoirfairelinux.com>
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04006 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040022#import <QPixmap>
23#import <QtMacExtras/qmacfunctions.h>
24
Anthony Léonard2382b562017-12-13 15:51:28 -050025// LRC
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040026#import <globalinstances.h>
Anthony Léonard2382b562017-12-13 15:51:28 -050027#import <api/interaction.h>
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040028
29#import "MessagesVC.h"
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040030#import "views/IMTableCellView.h"
31#import "views/MessageBubbleView.h"
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040032#import "views/NSImage+Extensions.h"
Anthony Léonard2382b562017-12-13 15:51:28 -050033#import "delegates/ImageManipulationDelegate.h"
Anthony Léonard6f819752018-01-05 09:53:40 -050034#import "utils.h"
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040035#import "views/NSColor+RingTheme.h"
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040036#import "views/IconButton.h"
37#import <QuickLook/QuickLook.h>
38#import <Quartz/Quartz.h>
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -040039#import <AVFoundation/AVFoundation.h>
40
41#import "RecordFileVC.h"
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040042
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040043
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040044@interface MessagesVC () <NSTableViewDelegate, NSTableViewDataSource, QLPreviewPanelDataSource> {
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040045
Anthony Léonard2382b562017-12-13 15:51:28 -050046 __unsafe_unretained IBOutlet NSTableView* conversationView;
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040047 __unsafe_unretained IBOutlet NSView* containerView;
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -050048 __unsafe_unretained IBOutlet NSTextField* messageField;
49 __unsafe_unretained IBOutlet IconButton *sendFileButton;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -040050 __unsafe_unretained IBOutlet IconButton *recordVideoButton;
51 __unsafe_unretained IBOutlet IconButton *recordAudioButton;
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -050052 __unsafe_unretained IBOutlet NSLayoutConstraint* sendPanelHeight;
53 __unsafe_unretained IBOutlet NSLayoutConstraint* messagesBottomMargin;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -040054 IBOutlet NSPopover *recordMessagePopover;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040055
Kateryna Kostiukc867eb92020-03-08 13:15:17 -040056 QString convUid_;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050057 lrc::api::ConversationModel* convModel_;
Anthony Léonard2382b562017-12-13 15:51:28 -050058 const lrc::api::conversation::Info* cachedConv_;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -040059 lrc::api::AVModel* avModel;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050060 QMetaObject::Connection newInteractionSignal_;
Anthony Léonard2382b562017-12-13 15:51:28 -050061
62 // Both are needed to invalidate cached conversation as pointer
63 // may not be referencing the same conversation anymore
64 QMetaObject::Connection modelSortedSignal_;
65 QMetaObject::Connection filterChangedSignal_;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050066 QMetaObject::Connection interactionStatusUpdatedSignal_;
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -040067 QMetaObject::Connection peerComposingMsgSignal_;
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040068 NSString* previewImage;
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -050069 NSMutableDictionary *pendingMessagesToSend;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -040070 RecordFileVC * recordingController;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040071}
72
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040073@end
74
Anthony Léonardf2bb17d2018-02-15 17:18:09 -050075// Tags for view
76NSInteger const GENERIC_INT_TEXT_TAG = 100;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040077NSInteger const GENERIC_INT_TIME_TAG = 200;
78
79// views size
80CGFloat const GENERIC_CELL_HEIGHT = 60;
81CGFloat const TIME_BOX_HEIGHT = 34;
82CGFloat const MESSAGE_TEXT_PADDING = 10;
83CGFloat const MAX_TRANSFERED_IMAGE_SIZE = 250;
84CGFloat const BUBBLE_HEIGHT_FOR_TRANSFERED_FILE = 87;
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -040085CGFloat const HEIGHT_FOR_COMPOSING_INDICATOR = 37;
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -050086NSInteger const MEESAGE_MARGIN = 21;
87NSInteger const SEND_PANEL_DEFAULT_HEIGHT = 60;
88NSInteger const SEND_PANEL_MAX_HEIGHT = 120;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -050089
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -040090BOOL peerComposingMessage = false;
91BOOL composingMessage = false;
92
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040093@implementation MessagesVC
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040094
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040095
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040096//MessageBuble type
97typedef NS_ENUM(NSInteger, MessageSequencing) {
98 SINGLE_WITH_TIME = 0,
99 SINGLE_WITHOUT_TIME = 1,
100 FIRST_WITH_TIME = 2,
101 FIRST_WITHOUT_TIME = 3,
102 MIDDLE_IN_SEQUENCE = 5,
103 LAST_IN_SEQUENCE = 6,
104};
105
106- (void)awakeFromNib
107{
108 NSNib *cellNib = [[NSNib alloc] initWithNibNamed:@"MessageCells" bundle:nil];
109 [conversationView registerNib:cellNib forIdentifier:@"LeftIncomingFileView"];
110 [conversationView registerNib:cellNib forIdentifier:@"LeftOngoingFileView"];
111 [conversationView registerNib:cellNib forIdentifier:@"LeftFinishedFileView"];
112 [conversationView registerNib:cellNib forIdentifier:@"RightOngoingFileView"];
113 [conversationView registerNib:cellNib forIdentifier:@"RightFinishedFileView"];
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400114 [conversationView registerNib:cellNib forIdentifier:@"PeerComposingMsgView"];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500115 [[conversationView.enclosingScrollView contentView] setCopiesOnScroll:NO];
116 [messageField setFocusRingType:NSFocusRingTypeNone];
117 [conversationView setWantsLayer:YES];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400118}
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500119
120- (instancetype)initWithCoder:(NSCoder *)coder
121{
122 self = [super initWithCoder:coder];
123 if (self) {
124 pendingMessagesToSend = [[NSMutableDictionary alloc] init];
125 }
126 return self;
127}
128
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400129-(void) setAVModel: (lrc::api::AVModel*) avmodel {
130 avModel = avmodel;
131 if (recordingController == nil) {
132 recordingController = [[RecordFileVC alloc] initWithNibName:@"RecordFileVC" bundle:nil avModel: self->avModel];
133 recordingController.delegate = self;
134 }
135}
136
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500137- (void)setMessage:(NSString *)newValue {
138 _message = [newValue removeEmptyLinesAtBorders];
139}
140
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400141-(void) clearData {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400142 if (!convUid_.isEmpty()) {
143 pendingMessagesToSend[convUid_.toNSString()] = messageField.stringValue;
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500144 }
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400145 cachedConv_ = nil;
146 convUid_ = "";
147 convModel_ = nil;
148
149 QObject::disconnect(modelSortedSignal_);
150 QObject::disconnect(filterChangedSignal_);
151 QObject::disconnect(interactionStatusUpdatedSignal_);
152 QObject::disconnect(newInteractionSignal_);
Kateryna Kostiuk5acaefd2020-03-25 11:14:25 -0400153 QObject::disconnect(peerComposingMsgSignal_);
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400154 [self closeRecordingView];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400155}
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400156
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500157-(void) scrollToBottom {
158 CGRect visibleRect = [conversationView enclosingScrollView].contentView.visibleRect;
159 NSRange range = [conversationView rowsInRect:visibleRect];
160 NSIndexSet* visibleIndexes = [NSIndexSet indexSetWithIndexesInRange:range];
161 NSUInteger lastvisibleRow = [visibleIndexes lastIndex];
162 if (([conversationView numberOfRows] > 0) &&
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400163 lastvisibleRow > ([conversationView numberOfRows] - 3)) {
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500164 [conversationView scrollToEndOfDocument:nil];
165 }
166}
167
Anthony Léonard2382b562017-12-13 15:51:28 -0500168-(const lrc::api::conversation::Info*) getCurrentConversation
169{
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400170 if (convModel_ == nil || convUid_.isEmpty())
Anthony Léonard2382b562017-12-13 15:51:28 -0500171 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400172
Anthony Léonard2382b562017-12-13 15:51:28 -0500173 if (cachedConv_ != nil)
174 return cachedConv_;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400175
Anthony Léonard6f819752018-01-05 09:53:40 -0500176 auto it = getConversationFromUid(convUid_, *convModel_);
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400177 if (it != convModel_->allFilteredConversations().end())
Anthony Léonard2382b562017-12-13 15:51:28 -0500178 cachedConv_ = &(*it);
179
180 return cachedConv_;
181}
182
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400183-(void) reloadConversationForMessage:(uint64_t) uid shouldUpdateHeight:(bool)update {
184 auto* conv = [self getCurrentConversation];
185
186 if (conv == nil)
187 return;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400188 auto it = conv->interactions.find(uid);
189 if (it == conv->interactions.end()) {
190 return;
191 }
192 auto itIndex = distance(conv->interactions.begin(),it);
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400193 NSRange rangeToUpdate = NSMakeRange(itIndex, 2);
194 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndexesInRange:rangeToUpdate];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400195 //reload previous message to update bubbleview
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400196 if (itIndex > 0) {
197 auto previousIt = it;
198 previousIt--;
199 auto previousInteraction = previousIt->second;
200 if (previousInteraction.type == lrc::api::interaction::Type::TEXT) {
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400201 NSRange range = NSMakeRange(itIndex - 1, 3);
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400202 indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
203 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400204 }
205 if (update) {
206 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
207 }
208 [conversationView reloadDataForRowIndexes: indexSet
209 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400210 CGRect visibleRect = [conversationView enclosingScrollView].contentView.visibleRect;
211 NSRange range = [conversationView rowsInRect:visibleRect];
212 NSIndexSet* visibleIndexes = [NSIndexSet indexSetWithIndexesInRange:range];
213 NSUInteger lastvisibleRow = [visibleIndexes lastIndex];
214 if (([conversationView numberOfRows] > 0) &&
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400215 lastvisibleRow > ([conversationView numberOfRows] - 3)) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400216 [conversationView scrollToEndOfDocument:nil];
217 }
218}
219
220-(void) reloadConversationForMessage:(uint64_t) uid shouldUpdateHeight:(bool)update updateConversation:(bool) updateConversation {
221 auto* conv = [self getCurrentConversation];
222
223 if (conv == nil)
224 return;
225 auto it = distance(conv->interactions.begin(),conv->interactions.find(uid));
226 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:it];
227 //reload previous message to update bubbleview
228 if (it > 0) {
229 NSRange range = NSMakeRange(it - 1, it);
230 indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
231 }
232 if (update) {
233 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
234 }
235 [conversationView reloadDataForRowIndexes: indexSet
236 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
237 if (update) {
238 [conversationView scrollToEndOfDocument:nil];
239 }
240}
241
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400242-(void)setConversationUid:(const QString&)convUid model:(lrc::api::ConversationModel *)model
Anthony Léonard2382b562017-12-13 15:51:28 -0500243{
244 if (convUid_ == convUid && convModel_ == model)
245 return;
246
247 cachedConv_ = nil;
248 convUid_ = convUid;
249 convModel_ = model;
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400250 peerComposingMessage = false;
251 composingMessage = false;
Anthony Léonard2382b562017-12-13 15:51:28 -0500252
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500253 // Signal triggered when messages are received or their status updated
254 QObject::disconnect(newInteractionSignal_);
255 QObject::disconnect(interactionStatusUpdatedSignal_);
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400256 QObject::disconnect(peerComposingMsgSignal_);
257
258 peerComposingMsgSignal_ = QObject::connect(convModel_,
259 &lrc::api::ConversationModel::composingStatusChanged,
260 [self](const QString &uid,
261 const QString &contactUri,
262 bool isComposing) {
263 if (uid != convUid_)
264 return;
265 bool shouldUpdate = isComposing != peerComposingMessage;
266 if (!shouldUpdate) {
267 return;
268 }
269 // reload and update height for composing indicator
270 peerComposingMessage = isComposing;
271 auto* conv = [self getCurrentConversation];
272 if (conv == nil)
273 return;
274 auto row = [conversationView numberOfRows] - 1;
275 if (row < 0) {
276 return;
277 }
278 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:row];
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400279 if(peerComposingMessage) {
280 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
281 } else {
282 //whait for possible incoming message to avoid view jumping
283 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
284 if (!peerComposingMessage) {
285 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
286 }
287 });
288 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400289 [conversationView reloadDataForRowIndexes: indexSet
290 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400291 CGRect visibleRect = [conversationView enclosingScrollView].contentView.visibleRect;
292 NSRange range = [conversationView rowsInRect:visibleRect];
293 NSIndexSet* visibleIndexes = [NSIndexSet indexSetWithIndexesInRange:range];
294 NSUInteger lastvisibleRow = [visibleIndexes lastIndex];
295 if (([conversationView numberOfRows] > 0) &&
296 lastvisibleRow > ([conversationView numberOfRows] - 3)) {
297 [conversationView scrollToEndOfDocument:nil];
298 }
299 });
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500300 newInteractionSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::newInteraction,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400301 [self](const QString& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400302 if (uid != convUid_)
303 return;
304 cachedConv_ = nil;
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400305 peerComposingMessage = false;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400306 [conversationView noteNumberOfRowsChanged];
307 [self reloadConversationForMessage:interactionId shouldUpdateHeight:YES];
308 });
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500309 interactionStatusUpdatedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::interactionStatusUpdated,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400310 [self](const QString& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500311 if (uid != convUid_)
312 return;
313 cachedConv_ = nil;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400314 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
315 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
316 convModel_->refreshFilter();
317 }
318 [self reloadConversationForMessage:interactionId shouldUpdateHeight:YES];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500319 });
Anthony Léonard2382b562017-12-13 15:51:28 -0500320
321 // Signals tracking changes in conversation list, we need them as cached conversation can be invalid
322 // after a reordering.
323 QObject::disconnect(modelSortedSignal_);
324 QObject::disconnect(filterChangedSignal_);
325 modelSortedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelSorted,
326 [self](){
327 cachedConv_ = nil;
328 });
329 filterChangedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::filterChanged,
330 [self](){
331 cachedConv_ = nil;
332 });
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400333 if (pendingMessagesToSend[convUid_.toNSString()]) {
334 self.message = pendingMessagesToSend[convUid_.toNSString()];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500335 [self updateSendMessageHeight];
336 } else {
337 self.message = @"";
338 if(messagesBottomMargin.constant != SEND_PANEL_DEFAULT_HEIGHT) {
339 sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
340 messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
341 [self scrollToBottom];
342 }
343 }
344 conversationView.alphaValue = 0.0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500345 [conversationView reloadData];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400346 [conversationView scrollToEndOfDocument:nil];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500347 CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"];
348 fadeIn.fromValue = [NSNumber numberWithFloat:0.0];
349 fadeIn.toValue = [NSNumber numberWithFloat:1.0];
350 fadeIn.duration = 0.4f;
351
352 [conversationView.layer addAnimation:fadeIn forKey:fadeIn.keyPath];
353 conversationView.alphaValue = 1;
354 auto* conv = [self getCurrentConversation];
355
356 if (conv == nil)
357 return;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400358 try {
359 [sendFileButton setEnabled:(convModel_->owner.contactModel->getContact(conv->participants[0]).profileInfo.type != lrc::api::profile::Type::SIP)];
360 } catch (std::out_of_range& e) {
361 NSLog(@"contact out of range");
362 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400363}
364
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400365#pragma mark - configure cells
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400366
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400367-(NSTableCellView*) makeGenericInteractionViewForTableView:(NSTableView*)tableView withText:(NSString*)text andTime:(NSString*) time
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500368{
369 NSTableCellView* result = [tableView makeViewWithIdentifier:@"GenericInteractionView" owner:self];
370 NSTextField* textField = [result viewWithTag:GENERIC_INT_TEXT_TAG];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400371 NSTextField* timeField = [result viewWithTag:GENERIC_INT_TIME_TAG];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500372
373 // TODO: Fix symbol in LRC
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500374 NSString* fixedString = [[text stringByReplacingOccurrencesOfString:@"🕽" withString:@""] stringByReplacingOccurrencesOfString:@"📞" withString:@""];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500375 [textField setStringValue:fixedString];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400376 [timeField setStringValue:time];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500377
378 return result;
379}
380
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400381-(NSTableCellView*) configureViewforTransfer:(lrc::api::interaction::Info)interaction interactionID: (uint64_t) interactionID tableView:(NSTableView*)tableView
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500382{
383 IMTableCellView* result;
384
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400385 auto type = interaction.type;
386 auto status = interaction.status;
387
388 NSString* fileName = @"incoming file";
389
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500390 // First, view is created
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400391 if (!interaction.authorUri.isEmpty()) {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500392 switch (status) {
393 case lrc::api::interaction::Status::TRANSFER_CREATED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400394 case lrc::api::interaction::Status::TRANSFER_AWAITING_HOST: {
395 result = [tableView makeViewWithIdentifier:@"LeftIncomingFileView" owner: conversationView];
396 [result.acceptButton setAction:@selector(acceptIncomingFile:)];
397 [result.acceptButton setTarget:self];
398 [result.declineButton setAction:@selector(declineIncomingFile:)];
399 [result.declineButton setTarget:self];
400 break;}
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500401 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400402 case lrc::api::interaction::Status::TRANSFER_ONGOING: {
403 result = [tableView makeViewWithIdentifier:@"LeftOngoingFileView" owner:conversationView];
404 [result.progressIndicator startAnimation:conversationView];
405 [result.declineButton setAction:@selector(declineIncomingFile:)];
406 [result.declineButton setTarget:self];
407 break;}
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500408 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400409 result = [tableView makeViewWithIdentifier:@"LeftFinishedFileView" owner:conversationView];
410 [result.transferedFileName setAction:@selector(imagePreview:)];
411 [result.transferedFileName setTarget:self];
412 [result.transferedFileName.cell setHighlightsBy:NSContentsCellMask];
413 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500414 case lrc::api::interaction::Status::TRANSFER_CANCELED:
415 case lrc::api::interaction::Status::TRANSFER_ERROR:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400416 result = [tableView makeViewWithIdentifier:@"LeftFinishedFileView" owner:conversationView];
417 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500418 }
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400419 } else {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400420 NSString* fileName = @"sent file";
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500421 switch (status) {
422 case lrc::api::interaction::Status::TRANSFER_CREATED:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500423 case lrc::api::interaction::Status::TRANSFER_ONGOING:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400424 case lrc::api::interaction::Status::TRANSFER_AWAITING_PEER:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500425 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400426 result = [tableView makeViewWithIdentifier:@"RightOngoingFileView" owner:conversationView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500427 [result.progressIndicator startAnimation:nil];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400428 [result.declineButton setAction:@selector(declineIncomingFile:)];
429 [result.declineButton setTarget:self];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500430 break;
431 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400432 result = [tableView makeViewWithIdentifier:@"RightFinishedFileView" owner:conversationView];
433 [result.transferedFileName setAction:@selector(imagePreview:)];
434 [result.transferedFileName setTarget:self];
435 [result.transferedFileName.cell setHighlightsBy:NSContentsCellMask];
436 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500437 case lrc::api::interaction::Status::TRANSFER_CANCELED:
438 case lrc::api::interaction::Status::TRANSFER_ERROR:
Olivier Soldanoe521a182018-02-26 16:55:19 -0500439 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400440 result = [tableView makeViewWithIdentifier:@"RightFinishedFileView" owner:conversationView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500441 }
442 }
443
444 // Then status label is updated if needed
445 switch (status) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400446 [result.statusLabel setTextColor:[NSColor textColor]];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500447 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400448 [result.statusLabel setTextColor:[NSColor greenSuccessColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500449 [result.statusLabel setStringValue:NSLocalizedString(@"Success", @"File transfer successful label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500450 break;
451 case lrc::api::interaction::Status::TRANSFER_CANCELED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400452 [result.statusLabel setTextColor:[NSColor orangeColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500453 [result.statusLabel setStringValue:NSLocalizedString(@"Canceled", @"File transfer canceled label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500454 break;
455 case lrc::api::interaction::Status::TRANSFER_ERROR:
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400456 [result.statusLabel setTextColor:[NSColor errorTransferColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500457 [result.statusLabel setStringValue:NSLocalizedString(@"Failed", @"File transfer failed label")];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500458 break;
459 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400460 [result.statusLabel setTextColor:[NSColor textColor]];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500461 [result.statusLabel setStringValue:NSLocalizedString(@"Unjoinable", @"File transfer peer unjoinable label")];
462 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500463 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400464 result.transferedImage.image = nil;
Kateryna Kostiukeaf1bc82018-10-12 14:33:50 -0400465 [result.openImagebutton setHidden:YES];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400466 [result.msgBackground setHidden:NO];
467 [result invalidateImageConstraints];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400468 NSString* name = interaction.body.toNSString();
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400469 if (name.length > 0) {
Kateryna Kostiuk67735232018-05-10 15:05:32 -0400470 fileName = [name lastPathComponent];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400471 }
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400472 NSFont *nameFont = [NSFont userFontOfSize:14.0];
473 NSColor *nameColor = [NSColor textColor];
474 NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
475 paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
476 paragraphStyle.alignment = NSTextAlignmentLeft;
477 NSDictionary *nameAttr = [NSDictionary dictionaryWithObjectsAndKeys:nameFont,NSFontAttributeName,
478 nameColor,NSForegroundColorAttributeName,
479 paragraphStyle,NSParagraphStyleAttributeName, nil];
480 NSAttributedString* nameAttributedString = [[NSAttributedString alloc] initWithString:fileName attributes:nameAttr];
481 result.transferedFileName.attributedTitle = nameAttributedString;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400482 if (status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400483 NSColor *higlightColor = [NSColor grayColor];
484 NSDictionary *alternativeNametAttr = [NSDictionary dictionaryWithObjectsAndKeys:nameFont,NSFontAttributeName,
485 higlightColor,NSForegroundColorAttributeName,
486 paragraphStyle,NSParagraphStyleAttributeName, nil];
487 NSAttributedString* alternativeString = [[NSAttributedString alloc] initWithString:fileName attributes:alternativeNametAttr];
488 result.transferedFileName.attributedAlternateTitle = alternativeString;
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400489 NSImage* image = [self getImageForFilePath:name];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400490 if (([name rangeOfString:@"/"].location == NSNotFound)) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400491 image = [self getImageForFilePath:[self getDataTransferPath:interactionID]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400492 }
493 if(image != nil) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400494 result.transferedImage.image = image;
495 [result updateImageConstraintWithMax: MAX_TRANSFERED_IMAGE_SIZE];
496 [result.openImagebutton setAction:@selector(imagePreview:)];
497 [result.openImagebutton setTarget:self];
Kateryna Kostiukeaf1bc82018-10-12 14:33:50 -0400498 [result.openImagebutton setHidden:NO];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400499 }
500 }
501 [result setupForInteraction:interactionID];
502 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
503 NSString* timeString = [self timeForMessage: msgTime];
504 result.timeLabel.stringValue = timeString;
505 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
506 if (!isOutgoing) {
507 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
508 auto* conv = [self getCurrentConversation];
509 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
510 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500511 return result;
512}
513
Anthony Léonard2382b562017-12-13 15:51:28 -0500514#pragma mark - NSTableViewDelegate methods
515- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400516{
517 return YES;
518}
519
Anthony Léonard2382b562017-12-13 15:51:28 -0500520- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400521{
Anthony Léonard2382b562017-12-13 15:51:28 -0500522 return NO;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400523}
524
Anthony Léonard2382b562017-12-13 15:51:28 -0500525- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400526{
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400527
Anthony Léonard2382b562017-12-13 15:51:28 -0500528 auto* conv = [self getCurrentConversation];
529
530 if (conv == nil)
531 return nil;
532
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400533 IMTableCellView* result;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500534 auto it = conv->interactions.begin();
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400535 auto size = conv->interactions.size();
536
537 if (row == size) {
Kateryna Kostiukb39ca192020-03-27 14:01:00 -0400538 if (size < 1) {
539 return [[NSView alloc] init];
540 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400541 //last row peer composing view
542 result = [tableView makeViewWithIdentifier:@"PeerComposingMsgView" owner:conversationView];
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400543 std::advance(it, row-1);
544 //if previous message not from peer display avatar
545 auto interaction = it->second;
546 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
547 [result.photoView setHidden: YES];
548 if(isOutgoing) {
549 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
550 auto* conv = [self getCurrentConversation];
551 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
552 [result.photoView setHidden: NO];
553 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400554 CGFloat alpha = peerComposingMessage ? 1 : 0;
555 result.alphaValue = 0;
556 [result animateCompozingIndicator: NO];
557 if (alpha == 1) {
558 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
559 if (peerComposingMessage) {
560 result.alphaValue = alpha;
561 [result animateCompozingIndicator: YES];
562 }
563 });
564 }
565 return result;
566 }
Anthony Léonard2382b562017-12-13 15:51:28 -0500567
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500568 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500569
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400570 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500571 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
572
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500573 switch (interaction.type) {
574 case lrc::api::interaction::Type::TEXT:
575 if (isOutgoing) {
576 result = [tableView makeViewWithIdentifier:@"RightMessageView" owner:self];
577 } else {
578 result = [tableView makeViewWithIdentifier:@"LeftMessageView" owner:self];
579 }
580 break;
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400581 case lrc::api::interaction::Type::DATA_TRANSFER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400582 return [self configureViewforTransfer:interaction interactionID: it->first tableView:tableView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500583 break;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500584 case lrc::api::interaction::Type::CONTACT:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400585 case lrc::api::interaction::Type::CALL: {
586 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
587 NSString* timeString = [self timeForMessage: msgTime];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400588 return [self makeGenericInteractionViewForTableView:tableView withText:interaction.body.toNSString() andTime:timeString];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400589 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500590 default: // If interaction is not of a known type
591 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400592 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400593 MessageSequencing sequence = [self computeSequencingFor:row];
594 BubbleType type = SINGLE;
595 if (sequence == FIRST_WITHOUT_TIME || sequence == FIRST_WITH_TIME) {
596 type = FIRST;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400597 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400598 if (sequence == MIDDLE_IN_SEQUENCE) {
599 type = MIDDLE;
600 }
601 if (sequence == LAST_IN_SEQUENCE) {
602 type = LAST;
603 }
604 result.msgBackground.type = type;
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400605 bool sendingFail = false;
606 [result.messageStatus setHidden:YES];
607 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
608 if (interaction.status == lrc::api::interaction::Status::SENDING) {
609 [result.messageStatus setHidden:NO];
610 [result.sendingMessageIndicator startAnimation:nil];
611 [result.messageFailed setHidden:YES];
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400612 } else if (interaction.status == lrc::api::interaction::Status::FAILURE) {
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400613 [result.messageStatus setHidden:NO];
614 [result.sendingMessageIndicator setHidden:YES];
615 [result.messageFailed setHidden:NO];
616 sendingFail = true;
617 }
618 }
619 [result setupForInteraction:it->first isFailed: sendingFail];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400620 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400621 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400622 [result.msgBackground setNeedsDisplay:YES];
623 [result setNeedsDisplay:YES];
624 [result.timeBox setNeedsDisplay:YES];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400625
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400626 NSString *text = interaction.body.toNSString();
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400627 text = [text removeEmptyLinesAtBorders];
628
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400629 NSMutableAttributedString* msgAttString =
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400630 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:text]
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400631 attributes:[self messageAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400632
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400633 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400634
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400635 [result updateMessageConstraint:messageSize.width andHeight:messageSize.height timeIsVisible:shouldDisplayTime isTopPadding: shouldApplyPadding];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400636 [[result.msgView textStorage] appendAttributedString:msgAttString];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500637 // [result.msgView checkTextInDocument:nil];
Anthony Léonard2382b562017-12-13 15:51:28 -0500638
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400639 NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
640 NSArray *matches = [linkDetector matchesInString:result.msgView.string options:0 range:NSMakeRange(0, result.msgView.string.length)];
641
642 [result.msgView.textStorage beginEditing];
643
644 for (NSTextCheckingResult *match in matches) {
645 if (!match.URL) continue;
646
647 NSDictionary *linkAttributes = @{
648 NSLinkAttributeName: match.URL,
649 };
650 [result.msgView.textStorage addAttributes:linkAttributes range:match.range];
651 }
652
653 [result.msgView.textStorage endEditing];
654
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400655 if (shouldDisplayTime) {
656 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
657 NSString* timeString = [self timeForMessage: msgTime];
658 result.timeLabel.stringValue = timeString;
Anthony Léonard64e19672018-01-18 16:40:34 -0500659 }
660
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400661 bool shouldDisplayAvatar = (sequence != MIDDLE_IN_SEQUENCE && sequence != FIRST_WITHOUT_TIME
662 && sequence != FIRST_WITH_TIME) ? YES : NO;
663 [result.photoView setHidden:!shouldDisplayAvatar];
664 if (!isOutgoing && shouldDisplayAvatar) {
665 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
666 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
667 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400668 return result;
669}
670
Anthony Léonard2382b562017-12-13 15:51:28 -0500671- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400672{
Anthony Léonard2382b562017-12-13 15:51:28 -0500673 double someWidth = tableView.frame.size.width * 0.7;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400674
Anthony Léonard2382b562017-12-13 15:51:28 -0500675 auto* conv = [self getCurrentConversation];
676
677 if (conv == nil)
678 return 0;
679
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400680 auto size = conv->interactions.size();
681 if (row == size) {
682 //last item peer composing view
683 if (peerComposingMessage) {
684 return HEIGHT_FOR_COMPOSING_INDICATOR;
685 }
686 return 0.1;
687 }
688
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500689 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500690
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500691 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500692
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400693 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500694
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400695 MessageSequencing sequence = [self computeSequencingFor:row];
696
697 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
698
699
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400700 if(interaction.type == lrc::api::interaction::Type::DATA_TRANSFER) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400701
702 if( interaction.status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400703 NSString* name = interaction.body.toNSString();
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400704 NSImage* image = [self getImageForFilePath:name];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400705 if (([name rangeOfString:@"/"].location == NSNotFound)) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400706 image = [self getImageForFilePath:[self getDataTransferPath:it->first]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400707 }
708 if (image != nil) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400709 CGFloat widthScaleFactor = MAX_TRANSFERED_IMAGE_SIZE / image.size.width;
710 CGFloat heightScaleFactor = MAX_TRANSFERED_IMAGE_SIZE / image.size.height;
711 CGFloat heigt = 0;
712 if((widthScaleFactor >= 1) && (heightScaleFactor >= 1)) {
713 heigt = image.size.height;
714 } else {
715 CGFloat scale = MIN(widthScaleFactor, heightScaleFactor);
716 heigt = image.size.height * scale;
717 }
718 return heigt + TIME_BOX_HEIGHT;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400719 }
720 }
721 return BUBBLE_HEIGHT_FOR_TRANSFERED_FILE + TIME_BOX_HEIGHT;
722 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500723
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500724 if(interaction.type == lrc::api::interaction::Type::CONTACT || interaction.type == lrc::api::interaction::Type::CALL)
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400725 return GENERIC_CELL_HEIGHT;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500726
Anthony Léonard2382b562017-12-13 15:51:28 -0500727 // TODO Implement interactions other than messages
728 if(interaction.type != lrc::api::interaction::Type::TEXT) {
729 return 0;
730 }
731
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400732 NSString *text = interaction.body.toNSString();
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400733 text = [text removeEmptyLinesAtBorders];
734
735 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400736 CGFloat singleLignMessageHeight = 15;
737
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400738 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
739
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400740 if (shouldDisplayTime) {
741 return MAX(messageSize.height + TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2,
742 TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2 + singleLignMessageHeight);
743 }
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400744 if(shouldApplyPadding) {
745 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2 + 15,
746 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2 + 15);
747 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400748 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2,
749 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2);
750}
751
752#pragma mark - message view parameters
753
754-(NSString *) getDataTransferPath:(uint64_t)interactionId {
755 lrc::api::datatransfer::Info info = {};
756 convModel_->getTransferInfo(interactionId, info);
757 double convertData = static_cast<double>(info.totalSize);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400758 return info.path.toNSString();
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400759}
760
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400761-(NSImage*) getImageForFilePath: (NSString *) path {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400762 if (path.length <= 0) {return nil;}
763 if (![[NSFileManager defaultManager] fileExistsAtPath: path]) {return nil;}
764 NSImage* transferedImage = [[NSImage alloc] initWithContentsOfFile: path];
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400765 return transferedImage;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400766}
767
768-(CGSize) sizeFor:(NSString *) message maxWidth:(CGFloat) width {
769 CGFloat horizaontalMargin = 6;
Anthony Léonard2382b562017-12-13 15:51:28 -0500770 NSMutableAttributedString* msgAttString =
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400771 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@", message]
Anthony Léonard2382b562017-12-13 15:51:28 -0500772 attributes:[self messageAttributes]];
773
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400774 CGFloat finalWidth = MIN(msgAttString.size.width + horizaontalMargin * 2, width);
775 NSRect frame = NSMakeRect(0, 0, finalWidth, msgAttString.size.height);
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400776 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400777 [[tv textStorage] setAttributedString:msgAttString];
778 [tv sizeToFit];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400779 return tv.frame.size;
780}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400781
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400782-(MessageSequencing) computeSequencingFor:(NSInteger) row {
783 auto* conv = [self getCurrentConversation];
784 if (conv == nil)
785 return SINGLE_WITHOUT_TIME;
786 auto it = conv->interactions.begin();
787 std::advance(it, row);
788 auto interaction = it->second;
789 if (interaction.type != lrc::api::interaction::Type::TEXT) {
790 return SINGLE_WITH_TIME;
791 }
792 if (row == 0) {
793 if (it == conv->interactions.end()) {
794 return SINGLE_WITH_TIME;
795 }
796 auto nextIt = it;
797 nextIt++;
798 auto nextInteraction = nextIt->second;
799 if ([self sequenceChangedFrom:interaction to: nextInteraction]) {
800 return SINGLE_WITH_TIME;
801 }
802 return FIRST_WITH_TIME;
803 }
804
805 if (row == conversationView.numberOfRows - 1) {
806 if(it == conv->interactions.begin()) {
807 return SINGLE_WITH_TIME;
808 }
809 auto previousIt = it;
810 previousIt--;
811 auto previousInteraction = previousIt->second;
812 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
813 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
814 if (!timeChanged && !authorChanged) {
815 return LAST_IN_SEQUENCE;
816 }
817 if (!timeChanged && authorChanged) {
818 return SINGLE_WITHOUT_TIME;
819 }
820 return SINGLE_WITH_TIME;
821 }
822 if(it == conv->interactions.begin() || it == conv->interactions.end()) {
823 return SINGLE_WITH_TIME;
824 }
825 auto previousIt = it;
826 previousIt--;
827 auto previousInteraction = previousIt->second;
828 auto nextIt = it;
829 nextIt++;
830 auto nextInteraction = nextIt->second;
831
832 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
833 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
834 bool sequenceWillChange = [self sequenceChangedFrom:interaction to: nextInteraction];
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400835 if (previousInteraction.type == lrc::api::interaction::Type::DATA_TRANSFER) {
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400836 if(!sequenceWillChange) {
837 return FIRST_WITH_TIME;
838 }
839 return SINGLE_WITH_TIME;
840 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400841 if (!sequenceWillChange) {
842 if (!timeChanged && !authorChanged) {
843 return MIDDLE_IN_SEQUENCE;
844 }
845 if (timeChanged) {
846 return FIRST_WITH_TIME;
847 }
848 return FIRST_WITHOUT_TIME;
849 } if (!timeChanged && !authorChanged) {
850 return LAST_IN_SEQUENCE;
851 } if (timeChanged) {
852 return SINGLE_WITH_TIME;
853 }
854 return SINGLE_WITHOUT_TIME;
855}
856
857-(bool) sequenceChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
858 return ([self sequenceTimeChangedFrom:firstInteraction to:secondInteraction] || [self sequenceAuthorChangedFrom:firstInteraction to:secondInteraction]);
859}
860
861-(bool) sequenceTimeChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
862 bool timeChanged = NO;
863 NSDate* firstMessageTime = [NSDate dateWithTimeIntervalSince1970:firstInteraction.timestamp];
864 NSDate* secondMessageTime = [NSDate dateWithTimeIntervalSince1970:secondInteraction.timestamp];
865 bool hourComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitHour];
866 bool minutComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitMinute];
867 if(hourComp != NSOrderedSame || minutComp != NSOrderedSame) {
868 timeChanged = YES;
869 }
870 return timeChanged;
871}
872
873-(bool) sequenceAuthorChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
874 bool authorChanged = YES;
875 bool isOutgoing = lrc::api::interaction::isOutgoing(firstInteraction);
876 if ((secondInteraction.type == lrc::api::interaction::Type::TEXT) && (isOutgoing == lrc::api::interaction::isOutgoing(secondInteraction))) {
877 authorChanged = NO;
878 }
879 return authorChanged;
880}
881
882-(NSString *)timeForMessage:(NSDate*) msgTime {
883 NSDate *today = [NSDate date];
884 NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
Kateryna Kostiukaf6d5e22018-06-12 15:00:00 -0400885 [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:[[NSLocale currentLocale] localeIdentifier]]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400886 if ([[NSCalendar currentCalendar] compareDate:today
887 toDate:msgTime
888 toUnitGranularity:NSCalendarUnitYear]!= NSOrderedSame) {
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500889 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterLongStyle timeStyle:NSDateFormatterMediumStyle];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400890 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400891 if ([[NSCalendar currentCalendar] compareDate:today
892 toDate:msgTime
893 toUnitGranularity:NSCalendarUnitDay]!= NSOrderedSame ||
894 [[NSCalendar currentCalendar] compareDate:today
895 toDate:msgTime
896 toUnitGranularity:NSCalendarUnitMonth]!= NSOrderedSame) {
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500897 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterShortStyle];
898 }
899 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterShortStyle];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400900}
901
Kateryna Kostiukf6317422018-09-27 17:08:20 -0400902- (void) updateSendMessageHeight {
903 NSAttributedString *msgAttString = messageField.attributedStringValue;
904 NSRect frame = NSMakeRect(0, 0, messageField.frame.size.width, msgAttString.size.height);
905 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
906 [[tv textStorage] setAttributedString:msgAttString];
907 [tv sizeToFit];
908 CGFloat height = tv.frame.size.height + MEESAGE_MARGIN * 2;
909 CGFloat newHeight = MIN(SEND_PANEL_MAX_HEIGHT, MAX(SEND_PANEL_DEFAULT_HEIGHT, height));
910 if(messagesBottomMargin.constant == newHeight) {
911 return;
912 }
913 messagesBottomMargin.constant = newHeight;
914 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
915 [self scrollToBottom];
916 sendPanelHeight.constant = newHeight;
917 });
918}
919
Anthony Léonard2382b562017-12-13 15:51:28 -0500920#pragma mark - NSTableViewDataSource
921
922- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
923{
924 auto* conv = [self getCurrentConversation];
925
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400926 // return conversation +1 view for composing indicator
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500927 if (conv)
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400928 return conv->interactions.size() + 1;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500929 else
930 return 0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500931}
932
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400933#pragma mark - Text formatting
934
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400935- (NSMutableDictionary*) messageAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400936{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400937 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400938 attrs[NSForegroundColorAttributeName] = [NSColor labelColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400939 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400940 return attrs;
941}
942
943- (NSParagraphStyle*) paragraphStyle
944{
945 /*
946 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
947 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
948 to copy, we use the default one.
949
950 The default values supplied by the default NSParagraphStyle are:
951 Alignment NSNaturalTextAlignment
952 Tab stops 12 left-aligned tabs, spaced by 28.0 points
953 Line break mode NSLineBreakByWordWrapping
954 All others 0.0
955 */
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400956 NSMutableParagraphStyle* aMutableParagraphStyle =
957 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
958 [aMutableParagraphStyle setHeadIndent:1.0];
959 [aMutableParagraphStyle setFirstLineHeadIndent:1.0];
960 return aMutableParagraphStyle;
961}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400962
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500963#pragma mark - Actions
964
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400965- (void)acceptIncomingFile:(id)sender {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500966 auto interId = [(IMTableCellView*)[[sender superview] superview] interaction];
967 auto& inter = [self getCurrentConversation]->interactions.find(interId)->second;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400968 if (convModel_ && !convUid_.isEmpty()) {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500969 NSSavePanel* filePicker = [NSSavePanel savePanel];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400970 [filePicker setNameFieldStringValue:inter.body.toNSString()];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500971
972 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
973 const char* fullPath = [[filePicker URL] fileSystemRepresentation];
974 convModel_->acceptTransfer(convUid_, interId, fullPath);
975 }
976 }
977}
978
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400979- (void)declineIncomingFile:(id)sender {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500980 auto inter = [(IMTableCellView*)[[sender superview] superview] interaction];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400981 if (convModel_ && !convUid_.isEmpty()) {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500982 convModel_->cancelTransfer(convUid_, inter);
983 }
984}
985
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400986- (void)imagePreview:(id)sender {
987 uint64_t interId;
988 if ([[sender superview] isKindOfClass:[IMTableCellView class]]) {
989 interId = [(IMTableCellView*)[sender superview] interaction];
990 } else if ([[[sender superview] superview] isKindOfClass:[IMTableCellView class]]) {
991 interId = [(IMTableCellView*)[[sender superview] superview] interaction];
992 } else {
993 return;
994 }
995 auto it = [self getCurrentConversation]->interactions.find(interId);
996 if (it == [self getCurrentConversation]->interactions.end()) {
997 return;
998 }
999 auto& interaction = it->second;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001000 NSString* name = interaction.body.toNSString();
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001001 if (([name rangeOfString:@"/"].location == NSNotFound)) {
1002 name = [self getDataTransferPath:interId];
1003 }
1004 previewImage = name;
1005 if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) {
1006 [[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
1007 } else {
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001008 dispatch_async(dispatch_get_main_queue(), ^{
1009 [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:self];
1010 });
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001011 }
1012}
1013
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001014- (IBAction)sendMessage:(id)sender {
1015 NSString* text = self.message;
Kateryna Kostiuka8b6b562019-02-01 13:26:18 -05001016 unichar separatorChar = NSLineSeparatorCharacter;
1017 NSString *separatorString = [NSString stringWithCharacters:&separatorChar length:1];
1018 text = [text stringByReplacingOccurrencesOfString: separatorString withString: @"\n"];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001019 if (text && text.length > 0) {
1020 auto* conv = [self getCurrentConversation];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001021 convModel_->sendMessage(convUid_, QString::fromNSString(text));
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001022 self.message = @"";
1023 if(sendPanelHeight.constant != SEND_PANEL_DEFAULT_HEIGHT) {
1024 sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
1025 messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
1026 [self scrollToBottom];
1027 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001028 if (composingMessage) {
1029 composingMessage = false;
1030 convModel_->setIsComposing(convUid_, composingMessage);
1031 }
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001032 }
1033}
1034
1035- (IBAction)openEmojy:(id)sender {
1036 [messageField.window makeFirstResponder: messageField];
1037 [[messageField currentEditor] moveToEndOfLine:nil];
1038 [NSApp orderFrontCharacterPalette: messageField];
1039}
1040
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001041- (IBAction)startVideoMessage:(id)sender
1042{
1043 [self startRecording:NO];
1044}
1045
1046- (IBAction)startAudioMessage:(id)sender
1047{
1048 [self startRecording:YES];
1049}
1050-(void) startRecording:(BOOL)isAudio {
1051 if (recordingController == nil) {
1052 recordingController = [[RecordFileVC alloc] initWithNibName:@"RecordFileVC" bundle:nil avModel: self->avModel];
1053 recordingController.delegate = self;
1054 }
1055 if(recordMessagePopover != nil)
1056 {
1057 [self closeRecordingView];
1058 return;
1059 }
1060 recordMessagePopover = [[NSPopover alloc] init];
1061 [recordingController prepareRecordingView: isAudio];
1062 [recordMessagePopover setContentSize: recordingController.view.frame.size];
1063 [recordMessagePopover setContentViewController:recordingController];
1064 [recordMessagePopover setAnimates:YES];
1065 NSButton *anchorButton = isAudio ? recordAudioButton : recordVideoButton;
1066 [recordMessagePopover showRelativeToRect: anchorButton.bounds
1067 ofView: anchorButton
1068 preferredEdge: NSMaxYEdge];
1069}
1070
1071-(void) sendFile:(NSString *) name withFilePath:(NSString *) path {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001072 convModel_->sendFile(convUid_, QString::fromNSString(path), QString::fromNSString(name));
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001073}
1074
1075-(void) closeRecordingView {
1076 if(recordMessagePopover != nil) {
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001077 [recordMessagePopover close];
1078 recordMessagePopover = nil;
Kateryna Kostiuka7404812019-10-28 12:24:46 -04001079 recordingController.stopRecordingView;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001080 }
1081}
1082
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001083- (IBAction)sendFile:(id)sender {
1084 NSOpenPanel* filePicker = [NSOpenPanel openPanel];
1085 [filePicker setCanChooseFiles:YES];
1086 [filePicker setCanChooseDirectories:NO];
1087 [filePicker setAllowsMultipleSelection:NO];
1088
1089 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
1090 if ([[filePicker URLs] count] == 1) {
1091 NSURL* url = [[filePicker URLs] objectAtIndex:0];
1092 const char* fullPath = [url fileSystemRepresentation];
1093 NSString* fileName = [url lastPathComponent];
1094 if (convModel_) {
1095 auto* conv = [self getCurrentConversation];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001096 convModel_->sendFile(convUid_, QString::fromStdString(fullPath), QString::fromNSString(fileName));
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001097 }
1098 }
1099 }
1100}
1101
1102
1103#pragma mark - NSTextFieldDelegate
1104
1105- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
1106{
1107 if (commandSelector == @selector(insertNewline:)) {
1108 if(self.message.length > 0) {
1109 [self sendMessage: nil];
1110 } else if(messagesBottomMargin.constant != SEND_PANEL_DEFAULT_HEIGHT) {
1111 sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
1112 messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
1113 [self scrollToBottom];
1114 }
1115 return YES;
1116 }
1117 return NO;
1118}
1119
1120- (void)controlTextDidChange:(NSNotification *)aNotification {
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001121 [self checkIfcomposingMsg];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001122 [self updateSendMessageHeight];
1123}
1124
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001125- (void) checkIfcomposingMsg {
1126 [self updateSendMessageHeight];
1127 BOOL haveText = [messageField.stringValue removeEmptyLinesAtBorders].length != 0;
1128 if (haveText != composingMessage) {
1129 composingMessage = haveText;
1130 convModel_->setIsComposing(convUid_, composingMessage);
1131 }
1132}
1133
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001134#pragma mark - QLPreviewPanelDataSource
1135
1136-(void)beginPreviewPanelControl:(QLPreviewPanel *)panel
1137{
1138 panel.dataSource = self;
1139}
1140
1141- (void)endPreviewPanelControl:(QLPreviewPanel *)panel {
1142 panel.dataSource = nil;
1143}
1144
1145-(BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel
1146{
1147 return YES;
1148}
1149
1150- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel {
1151 return 1;
1152}
1153
1154- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index {
1155 return [NSURL fileURLWithPath:previewImage];
1156}
1157
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04001158@end