blob: 32a2831a46848c40474ea201c01730c94e110106 [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) {
538 //last row peer composing view
539 result = [tableView makeViewWithIdentifier:@"PeerComposingMsgView" owner:conversationView];
Kateryna Kostiukf5eb58c2020-03-25 16:50:35 -0400540 std::advance(it, row-1);
541 //if previous message not from peer display avatar
542 auto interaction = it->second;
543 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
544 [result.photoView setHidden: YES];
545 if(isOutgoing) {
546 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
547 auto* conv = [self getCurrentConversation];
548 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
549 [result.photoView setHidden: NO];
550 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400551 CGFloat alpha = peerComposingMessage ? 1 : 0;
552 result.alphaValue = 0;
553 [result animateCompozingIndicator: NO];
554 if (alpha == 1) {
555 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
556 if (peerComposingMessage) {
557 result.alphaValue = alpha;
558 [result animateCompozingIndicator: YES];
559 }
560 });
561 }
562 return result;
563 }
Anthony Léonard2382b562017-12-13 15:51:28 -0500564
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500565 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500566
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400567 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500568 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
569
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500570 switch (interaction.type) {
571 case lrc::api::interaction::Type::TEXT:
572 if (isOutgoing) {
573 result = [tableView makeViewWithIdentifier:@"RightMessageView" owner:self];
574 } else {
575 result = [tableView makeViewWithIdentifier:@"LeftMessageView" owner:self];
576 }
577 break;
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400578 case lrc::api::interaction::Type::DATA_TRANSFER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400579 return [self configureViewforTransfer:interaction interactionID: it->first tableView:tableView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500580 break;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500581 case lrc::api::interaction::Type::CONTACT:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400582 case lrc::api::interaction::Type::CALL: {
583 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
584 NSString* timeString = [self timeForMessage: msgTime];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400585 return [self makeGenericInteractionViewForTableView:tableView withText:interaction.body.toNSString() andTime:timeString];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400586 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500587 default: // If interaction is not of a known type
588 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400589 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400590 MessageSequencing sequence = [self computeSequencingFor:row];
591 BubbleType type = SINGLE;
592 if (sequence == FIRST_WITHOUT_TIME || sequence == FIRST_WITH_TIME) {
593 type = FIRST;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400594 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400595 if (sequence == MIDDLE_IN_SEQUENCE) {
596 type = MIDDLE;
597 }
598 if (sequence == LAST_IN_SEQUENCE) {
599 type = LAST;
600 }
601 result.msgBackground.type = type;
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400602 bool sendingFail = false;
603 [result.messageStatus setHidden:YES];
604 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
605 if (interaction.status == lrc::api::interaction::Status::SENDING) {
606 [result.messageStatus setHidden:NO];
607 [result.sendingMessageIndicator startAnimation:nil];
608 [result.messageFailed setHidden:YES];
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400609 } else if (interaction.status == lrc::api::interaction::Status::FAILURE) {
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400610 [result.messageStatus setHidden:NO];
611 [result.sendingMessageIndicator setHidden:YES];
612 [result.messageFailed setHidden:NO];
613 sendingFail = true;
614 }
615 }
616 [result setupForInteraction:it->first isFailed: sendingFail];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400617 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400618 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400619 [result.msgBackground setNeedsDisplay:YES];
620 [result setNeedsDisplay:YES];
621 [result.timeBox setNeedsDisplay:YES];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400622
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400623 NSString *text = interaction.body.toNSString();
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400624 text = [text removeEmptyLinesAtBorders];
625
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400626 NSMutableAttributedString* msgAttString =
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400627 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:text]
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400628 attributes:[self messageAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400629
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400630 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400631
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400632 [result updateMessageConstraint:messageSize.width andHeight:messageSize.height timeIsVisible:shouldDisplayTime isTopPadding: shouldApplyPadding];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400633 [[result.msgView textStorage] appendAttributedString:msgAttString];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -0500634 // [result.msgView checkTextInDocument:nil];
Anthony Léonard2382b562017-12-13 15:51:28 -0500635
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400636 NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
637 NSArray *matches = [linkDetector matchesInString:result.msgView.string options:0 range:NSMakeRange(0, result.msgView.string.length)];
638
639 [result.msgView.textStorage beginEditing];
640
641 for (NSTextCheckingResult *match in matches) {
642 if (!match.URL) continue;
643
644 NSDictionary *linkAttributes = @{
645 NSLinkAttributeName: match.URL,
646 };
647 [result.msgView.textStorage addAttributes:linkAttributes range:match.range];
648 }
649
650 [result.msgView.textStorage endEditing];
651
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400652 if (shouldDisplayTime) {
653 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
654 NSString* timeString = [self timeForMessage: msgTime];
655 result.timeLabel.stringValue = timeString;
Anthony Léonard64e19672018-01-18 16:40:34 -0500656 }
657
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400658 bool shouldDisplayAvatar = (sequence != MIDDLE_IN_SEQUENCE && sequence != FIRST_WITHOUT_TIME
659 && sequence != FIRST_WITH_TIME) ? YES : NO;
660 [result.photoView setHidden:!shouldDisplayAvatar];
661 if (!isOutgoing && shouldDisplayAvatar) {
662 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
663 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
664 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400665 return result;
666}
667
Anthony Léonard2382b562017-12-13 15:51:28 -0500668- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400669{
Anthony Léonard2382b562017-12-13 15:51:28 -0500670 double someWidth = tableView.frame.size.width * 0.7;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400671
Anthony Léonard2382b562017-12-13 15:51:28 -0500672 auto* conv = [self getCurrentConversation];
673
674 if (conv == nil)
675 return 0;
676
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400677 auto size = conv->interactions.size();
678 if (row == size) {
679 //last item peer composing view
680 if (peerComposingMessage) {
681 return HEIGHT_FOR_COMPOSING_INDICATOR;
682 }
683 return 0.1;
684 }
685
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500686 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500687
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500688 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500689
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400690 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500691
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400692 MessageSequencing sequence = [self computeSequencingFor:row];
693
694 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
695
696
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400697 if(interaction.type == lrc::api::interaction::Type::DATA_TRANSFER) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400698
699 if( interaction.status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400700 NSString* name = interaction.body.toNSString();
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400701 NSImage* image = [self getImageForFilePath:name];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400702 if (([name rangeOfString:@"/"].location == NSNotFound)) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400703 image = [self getImageForFilePath:[self getDataTransferPath:it->first]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400704 }
705 if (image != nil) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400706 CGFloat widthScaleFactor = MAX_TRANSFERED_IMAGE_SIZE / image.size.width;
707 CGFloat heightScaleFactor = MAX_TRANSFERED_IMAGE_SIZE / image.size.height;
708 CGFloat heigt = 0;
709 if((widthScaleFactor >= 1) && (heightScaleFactor >= 1)) {
710 heigt = image.size.height;
711 } else {
712 CGFloat scale = MIN(widthScaleFactor, heightScaleFactor);
713 heigt = image.size.height * scale;
714 }
715 return heigt + TIME_BOX_HEIGHT;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400716 }
717 }
718 return BUBBLE_HEIGHT_FOR_TRANSFERED_FILE + TIME_BOX_HEIGHT;
719 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500720
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500721 if(interaction.type == lrc::api::interaction::Type::CONTACT || interaction.type == lrc::api::interaction::Type::CALL)
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400722 return GENERIC_CELL_HEIGHT;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500723
Anthony Léonard2382b562017-12-13 15:51:28 -0500724 // TODO Implement interactions other than messages
725 if(interaction.type != lrc::api::interaction::Type::TEXT) {
726 return 0;
727 }
728
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400729 NSString *text = interaction.body.toNSString();
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400730 text = [text removeEmptyLinesAtBorders];
731
732 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400733 CGFloat singleLignMessageHeight = 15;
734
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400735 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
736
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400737 if (shouldDisplayTime) {
738 return MAX(messageSize.height + TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2,
739 TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2 + singleLignMessageHeight);
740 }
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400741 if(shouldApplyPadding) {
742 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2 + 15,
743 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2 + 15);
744 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400745 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2,
746 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2);
747}
748
749#pragma mark - message view parameters
750
751-(NSString *) getDataTransferPath:(uint64_t)interactionId {
752 lrc::api::datatransfer::Info info = {};
753 convModel_->getTransferInfo(interactionId, info);
754 double convertData = static_cast<double>(info.totalSize);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400755 return info.path.toNSString();
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400756}
757
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400758-(NSImage*) getImageForFilePath: (NSString *) path {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400759 if (path.length <= 0) {return nil;}
760 if (![[NSFileManager defaultManager] fileExistsAtPath: path]) {return nil;}
761 NSImage* transferedImage = [[NSImage alloc] initWithContentsOfFile: path];
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400762 return transferedImage;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400763}
764
765-(CGSize) sizeFor:(NSString *) message maxWidth:(CGFloat) width {
766 CGFloat horizaontalMargin = 6;
Anthony Léonard2382b562017-12-13 15:51:28 -0500767 NSMutableAttributedString* msgAttString =
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400768 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@", message]
Anthony Léonard2382b562017-12-13 15:51:28 -0500769 attributes:[self messageAttributes]];
770
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400771 CGFloat finalWidth = MIN(msgAttString.size.width + horizaontalMargin * 2, width);
772 NSRect frame = NSMakeRect(0, 0, finalWidth, msgAttString.size.height);
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400773 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400774 [[tv textStorage] setAttributedString:msgAttString];
775 [tv sizeToFit];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400776 return tv.frame.size;
777}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400778
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400779-(MessageSequencing) computeSequencingFor:(NSInteger) row {
780 auto* conv = [self getCurrentConversation];
781 if (conv == nil)
782 return SINGLE_WITHOUT_TIME;
783 auto it = conv->interactions.begin();
784 std::advance(it, row);
785 auto interaction = it->second;
786 if (interaction.type != lrc::api::interaction::Type::TEXT) {
787 return SINGLE_WITH_TIME;
788 }
789 if (row == 0) {
790 if (it == conv->interactions.end()) {
791 return SINGLE_WITH_TIME;
792 }
793 auto nextIt = it;
794 nextIt++;
795 auto nextInteraction = nextIt->second;
796 if ([self sequenceChangedFrom:interaction to: nextInteraction]) {
797 return SINGLE_WITH_TIME;
798 }
799 return FIRST_WITH_TIME;
800 }
801
802 if (row == conversationView.numberOfRows - 1) {
803 if(it == conv->interactions.begin()) {
804 return SINGLE_WITH_TIME;
805 }
806 auto previousIt = it;
807 previousIt--;
808 auto previousInteraction = previousIt->second;
809 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
810 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
811 if (!timeChanged && !authorChanged) {
812 return LAST_IN_SEQUENCE;
813 }
814 if (!timeChanged && authorChanged) {
815 return SINGLE_WITHOUT_TIME;
816 }
817 return SINGLE_WITH_TIME;
818 }
819 if(it == conv->interactions.begin() || it == conv->interactions.end()) {
820 return SINGLE_WITH_TIME;
821 }
822 auto previousIt = it;
823 previousIt--;
824 auto previousInteraction = previousIt->second;
825 auto nextIt = it;
826 nextIt++;
827 auto nextInteraction = nextIt->second;
828
829 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
830 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
831 bool sequenceWillChange = [self sequenceChangedFrom:interaction to: nextInteraction];
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400832 if (previousInteraction.type == lrc::api::interaction::Type::DATA_TRANSFER) {
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400833 if(!sequenceWillChange) {
834 return FIRST_WITH_TIME;
835 }
836 return SINGLE_WITH_TIME;
837 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400838 if (!sequenceWillChange) {
839 if (!timeChanged && !authorChanged) {
840 return MIDDLE_IN_SEQUENCE;
841 }
842 if (timeChanged) {
843 return FIRST_WITH_TIME;
844 }
845 return FIRST_WITHOUT_TIME;
846 } if (!timeChanged && !authorChanged) {
847 return LAST_IN_SEQUENCE;
848 } if (timeChanged) {
849 return SINGLE_WITH_TIME;
850 }
851 return SINGLE_WITHOUT_TIME;
852}
853
854-(bool) sequenceChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
855 return ([self sequenceTimeChangedFrom:firstInteraction to:secondInteraction] || [self sequenceAuthorChangedFrom:firstInteraction to:secondInteraction]);
856}
857
858-(bool) sequenceTimeChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
859 bool timeChanged = NO;
860 NSDate* firstMessageTime = [NSDate dateWithTimeIntervalSince1970:firstInteraction.timestamp];
861 NSDate* secondMessageTime = [NSDate dateWithTimeIntervalSince1970:secondInteraction.timestamp];
862 bool hourComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitHour];
863 bool minutComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitMinute];
864 if(hourComp != NSOrderedSame || minutComp != NSOrderedSame) {
865 timeChanged = YES;
866 }
867 return timeChanged;
868}
869
870-(bool) sequenceAuthorChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
871 bool authorChanged = YES;
872 bool isOutgoing = lrc::api::interaction::isOutgoing(firstInteraction);
873 if ((secondInteraction.type == lrc::api::interaction::Type::TEXT) && (isOutgoing == lrc::api::interaction::isOutgoing(secondInteraction))) {
874 authorChanged = NO;
875 }
876 return authorChanged;
877}
878
879-(NSString *)timeForMessage:(NSDate*) msgTime {
880 NSDate *today = [NSDate date];
881 NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
Kateryna Kostiukaf6d5e22018-06-12 15:00:00 -0400882 [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:[[NSLocale currentLocale] localeIdentifier]]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400883 if ([[NSCalendar currentCalendar] compareDate:today
884 toDate:msgTime
885 toUnitGranularity:NSCalendarUnitYear]!= NSOrderedSame) {
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500886 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterLongStyle timeStyle:NSDateFormatterMediumStyle];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400887 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400888 if ([[NSCalendar currentCalendar] compareDate:today
889 toDate:msgTime
890 toUnitGranularity:NSCalendarUnitDay]!= NSOrderedSame ||
891 [[NSCalendar currentCalendar] compareDate:today
892 toDate:msgTime
893 toUnitGranularity:NSCalendarUnitMonth]!= NSOrderedSame) {
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500894 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterShortStyle];
895 }
896 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterShortStyle];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400897}
898
Kateryna Kostiukf6317422018-09-27 17:08:20 -0400899- (void) updateSendMessageHeight {
900 NSAttributedString *msgAttString = messageField.attributedStringValue;
901 NSRect frame = NSMakeRect(0, 0, messageField.frame.size.width, msgAttString.size.height);
902 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
903 [[tv textStorage] setAttributedString:msgAttString];
904 [tv sizeToFit];
905 CGFloat height = tv.frame.size.height + MEESAGE_MARGIN * 2;
906 CGFloat newHeight = MIN(SEND_PANEL_MAX_HEIGHT, MAX(SEND_PANEL_DEFAULT_HEIGHT, height));
907 if(messagesBottomMargin.constant == newHeight) {
908 return;
909 }
910 messagesBottomMargin.constant = newHeight;
911 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
912 [self scrollToBottom];
913 sendPanelHeight.constant = newHeight;
914 });
915}
916
Anthony Léonard2382b562017-12-13 15:51:28 -0500917#pragma mark - NSTableViewDataSource
918
919- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
920{
921 auto* conv = [self getCurrentConversation];
922
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400923 // return conversation +1 view for composing indicator
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500924 if (conv)
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -0400925 return conv->interactions.size() + 1;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500926 else
927 return 0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500928}
929
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400930#pragma mark - Text formatting
931
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400932- (NSMutableDictionary*) messageAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400933{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400934 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400935 attrs[NSForegroundColorAttributeName] = [NSColor labelColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400936 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400937 return attrs;
938}
939
940- (NSParagraphStyle*) paragraphStyle
941{
942 /*
943 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
944 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
945 to copy, we use the default one.
946
947 The default values supplied by the default NSParagraphStyle are:
948 Alignment NSNaturalTextAlignment
949 Tab stops 12 left-aligned tabs, spaced by 28.0 points
950 Line break mode NSLineBreakByWordWrapping
951 All others 0.0
952 */
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400953 NSMutableParagraphStyle* aMutableParagraphStyle =
954 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
955 [aMutableParagraphStyle setHeadIndent:1.0];
956 [aMutableParagraphStyle setFirstLineHeadIndent:1.0];
957 return aMutableParagraphStyle;
958}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400959
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500960#pragma mark - Actions
961
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400962- (void)acceptIncomingFile:(id)sender {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500963 auto interId = [(IMTableCellView*)[[sender superview] superview] interaction];
964 auto& inter = [self getCurrentConversation]->interactions.find(interId)->second;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400965 if (convModel_ && !convUid_.isEmpty()) {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500966 NSSavePanel* filePicker = [NSSavePanel savePanel];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400967 [filePicker setNameFieldStringValue:inter.body.toNSString()];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500968
969 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
970 const char* fullPath = [[filePicker URL] fileSystemRepresentation];
971 convModel_->acceptTransfer(convUid_, interId, fullPath);
972 }
973 }
974}
975
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400976- (void)declineIncomingFile:(id)sender {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500977 auto inter = [(IMTableCellView*)[[sender superview] superview] interaction];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400978 if (convModel_ && !convUid_.isEmpty()) {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500979 convModel_->cancelTransfer(convUid_, inter);
980 }
981}
982
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400983- (void)imagePreview:(id)sender {
984 uint64_t interId;
985 if ([[sender superview] isKindOfClass:[IMTableCellView class]]) {
986 interId = [(IMTableCellView*)[sender superview] interaction];
987 } else if ([[[sender superview] superview] isKindOfClass:[IMTableCellView class]]) {
988 interId = [(IMTableCellView*)[[sender superview] superview] interaction];
989 } else {
990 return;
991 }
992 auto it = [self getCurrentConversation]->interactions.find(interId);
993 if (it == [self getCurrentConversation]->interactions.end()) {
994 return;
995 }
996 auto& interaction = it->second;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400997 NSString* name = interaction.body.toNSString();
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400998 if (([name rangeOfString:@"/"].location == NSNotFound)) {
999 name = [self getDataTransferPath:interId];
1000 }
1001 previewImage = name;
1002 if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) {
1003 [[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
1004 } else {
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001005 dispatch_async(dispatch_get_main_queue(), ^{
1006 [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:self];
1007 });
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -04001008 }
1009}
1010
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001011- (IBAction)sendMessage:(id)sender {
1012 NSString* text = self.message;
Kateryna Kostiuka8b6b562019-02-01 13:26:18 -05001013 unichar separatorChar = NSLineSeparatorCharacter;
1014 NSString *separatorString = [NSString stringWithCharacters:&separatorChar length:1];
1015 text = [text stringByReplacingOccurrencesOfString: separatorString withString: @"\n"];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001016 if (text && text.length > 0) {
1017 auto* conv = [self getCurrentConversation];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001018 convModel_->sendMessage(convUid_, QString::fromNSString(text));
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001019 self.message = @"";
1020 if(sendPanelHeight.constant != SEND_PANEL_DEFAULT_HEIGHT) {
1021 sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
1022 messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
1023 [self scrollToBottom];
1024 }
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001025 if (composingMessage) {
1026 composingMessage = false;
1027 convModel_->setIsComposing(convUid_, composingMessage);
1028 }
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001029 }
1030}
1031
1032- (IBAction)openEmojy:(id)sender {
1033 [messageField.window makeFirstResponder: messageField];
1034 [[messageField currentEditor] moveToEndOfLine:nil];
1035 [NSApp orderFrontCharacterPalette: messageField];
1036}
1037
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001038- (IBAction)startVideoMessage:(id)sender
1039{
1040 [self startRecording:NO];
1041}
1042
1043- (IBAction)startAudioMessage:(id)sender
1044{
1045 [self startRecording:YES];
1046}
1047-(void) startRecording:(BOOL)isAudio {
1048 if (recordingController == nil) {
1049 recordingController = [[RecordFileVC alloc] initWithNibName:@"RecordFileVC" bundle:nil avModel: self->avModel];
1050 recordingController.delegate = self;
1051 }
1052 if(recordMessagePopover != nil)
1053 {
1054 [self closeRecordingView];
1055 return;
1056 }
1057 recordMessagePopover = [[NSPopover alloc] init];
1058 [recordingController prepareRecordingView: isAudio];
1059 [recordMessagePopover setContentSize: recordingController.view.frame.size];
1060 [recordMessagePopover setContentViewController:recordingController];
1061 [recordMessagePopover setAnimates:YES];
1062 NSButton *anchorButton = isAudio ? recordAudioButton : recordVideoButton;
1063 [recordMessagePopover showRelativeToRect: anchorButton.bounds
1064 ofView: anchorButton
1065 preferredEdge: NSMaxYEdge];
1066}
1067
1068-(void) sendFile:(NSString *) name withFilePath:(NSString *) path {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001069 convModel_->sendFile(convUid_, QString::fromNSString(path), QString::fromNSString(name));
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001070}
1071
1072-(void) closeRecordingView {
1073 if(recordMessagePopover != nil) {
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001074 [recordMessagePopover close];
1075 recordMessagePopover = nil;
Kateryna Kostiuka7404812019-10-28 12:24:46 -04001076 recordingController.stopRecordingView;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001077 }
1078}
1079
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001080- (IBAction)sendFile:(id)sender {
1081 NSOpenPanel* filePicker = [NSOpenPanel openPanel];
1082 [filePicker setCanChooseFiles:YES];
1083 [filePicker setCanChooseDirectories:NO];
1084 [filePicker setAllowsMultipleSelection:NO];
1085
1086 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
1087 if ([[filePicker URLs] count] == 1) {
1088 NSURL* url = [[filePicker URLs] objectAtIndex:0];
1089 const char* fullPath = [url fileSystemRepresentation];
1090 NSString* fileName = [url lastPathComponent];
1091 if (convModel_) {
1092 auto* conv = [self getCurrentConversation];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001093 convModel_->sendFile(convUid_, QString::fromStdString(fullPath), QString::fromNSString(fileName));
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001094 }
1095 }
1096 }
1097}
1098
1099
1100#pragma mark - NSTextFieldDelegate
1101
1102- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
1103{
1104 if (commandSelector == @selector(insertNewline:)) {
1105 if(self.message.length > 0) {
1106 [self sendMessage: nil];
1107 } else if(messagesBottomMargin.constant != SEND_PANEL_DEFAULT_HEIGHT) {
1108 sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
1109 messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
1110 [self scrollToBottom];
1111 }
1112 return YES;
1113 }
1114 return NO;
1115}
1116
1117- (void)controlTextDidChange:(NSNotification *)aNotification {
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001118 [self checkIfcomposingMsg];
Kateryna Kostiuk4f37d952018-12-04 13:19:17 -05001119 [self updateSendMessageHeight];
1120}
1121
Kateryna Kostiuk10c896b2020-03-24 12:46:17 -04001122- (void) checkIfcomposingMsg {
1123 [self updateSendMessageHeight];
1124 BOOL haveText = [messageField.stringValue removeEmptyLinesAtBorders].length != 0;
1125 if (haveText != composingMessage) {
1126 composingMessage = haveText;
1127 convModel_->setIsComposing(convUid_, composingMessage);
1128 }
1129}
1130
Kateryna Kostiukf6317422018-09-27 17:08:20 -04001131#pragma mark - QLPreviewPanelDataSource
1132
1133-(void)beginPreviewPanelControl:(QLPreviewPanel *)panel
1134{
1135 panel.dataSource = self;
1136}
1137
1138- (void)endPreviewPanelControl:(QLPreviewPanel *)panel {
1139 panel.dataSource = nil;
1140}
1141
1142-(BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel
1143{
1144 return YES;
1145}
1146
1147- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel {
1148 return 1;
1149}
1150
1151- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index {
1152 return [NSURL fileURLWithPath:previewImage];
1153}
1154
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04001155@end