blob: e956e7183cea1ec8be99ff9e072a7592c12f1fc7 [file] [log] [blame]
Andreas Traczyk252a94a2018-04-20 16:36:20 -04001
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04002/*
Anthony Léonarde7d62ed2018-01-25 10:51:47 -05003 * Copyright (C) 2015-2018 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
22#import <QItemSelectionModel>
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040023#import <QPixmap>
24#import <QtMacExtras/qmacfunctions.h>
25
Anthony Léonard2382b562017-12-13 15:51:28 -050026// LRC
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040027#import <globalinstances.h>
Anthony Léonard2382b562017-12-13 15:51:28 -050028#import <api/interaction.h>
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040029
30#import "MessagesVC.h"
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040031#import "views/IMTableCellView.h"
32#import "views/MessageBubbleView.h"
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040033#import "views/NSImage+Extensions.h"
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040034#import "INDSequentialTextSelectionManager.h"
Anthony Léonard2382b562017-12-13 15:51:28 -050035#import "delegates/ImageManipulationDelegate.h"
Anthony Léonard6f819752018-01-05 09:53:40 -050036#import "utils.h"
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040037#import "views/NSColor+RingTheme.h"
Kateryna Kostiuka0f16862018-05-04 09:11:41 -040038#import "NSString+Extensions.h"
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040039
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040040
Anthony Léonard2382b562017-12-13 15:51:28 -050041@interface MessagesVC () <NSTableViewDelegate, NSTableViewDataSource> {
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040042
Anthony Léonard2382b562017-12-13 15:51:28 -050043 __unsafe_unretained IBOutlet NSTableView* conversationView;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040044
Anthony Léonard2382b562017-12-13 15:51:28 -050045 std::string convUid_;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050046 lrc::api::ConversationModel* convModel_;
Anthony Léonard2382b562017-12-13 15:51:28 -050047 const lrc::api::conversation::Info* cachedConv_;
48
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050049 QMetaObject::Connection newInteractionSignal_;
Anthony Léonard2382b562017-12-13 15:51:28 -050050
51 // Both are needed to invalidate cached conversation as pointer
52 // may not be referencing the same conversation anymore
53 QMetaObject::Connection modelSortedSignal_;
54 QMetaObject::Connection filterChangedSignal_;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050055 QMetaObject::Connection interactionStatusUpdatedSignal_;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040056}
57
58@property (nonatomic, strong, readonly) INDSequentialTextSelectionManager* selectionManager;
59
60@end
61
Anthony Léonardf2bb17d2018-02-15 17:18:09 -050062// Tags for view
63NSInteger const GENERIC_INT_TEXT_TAG = 100;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040064NSInteger const GENERIC_INT_TIME_TAG = 200;
65
66// views size
67CGFloat const GENERIC_CELL_HEIGHT = 60;
68CGFloat const TIME_BOX_HEIGHT = 34;
69CGFloat const MESSAGE_TEXT_PADDING = 10;
70CGFloat const MAX_TRANSFERED_IMAGE_SIZE = 250;
71CGFloat const BUBBLE_HEIGHT_FOR_TRANSFERED_FILE = 87;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -050072
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040073@implementation MessagesVC
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040074
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040075//MessageBuble type
76typedef NS_ENUM(NSInteger, MessageSequencing) {
77 SINGLE_WITH_TIME = 0,
78 SINGLE_WITHOUT_TIME = 1,
79 FIRST_WITH_TIME = 2,
80 FIRST_WITHOUT_TIME = 3,
81 MIDDLE_IN_SEQUENCE = 5,
82 LAST_IN_SEQUENCE = 6,
83};
84
85- (void)awakeFromNib
86{
87 NSNib *cellNib = [[NSNib alloc] initWithNibNamed:@"MessageCells" bundle:nil];
88 [conversationView registerNib:cellNib forIdentifier:@"LeftIncomingFileView"];
89 [conversationView registerNib:cellNib forIdentifier:@"LeftOngoingFileView"];
90 [conversationView registerNib:cellNib forIdentifier:@"LeftFinishedFileView"];
91 [conversationView registerNib:cellNib forIdentifier:@"RightOngoingFileView"];
92 [conversationView registerNib:cellNib forIdentifier:@"RightFinishedFileView"];
93}
Andreas Traczyk252a94a2018-04-20 16:36:20 -040094-(void) clearData {
95 cachedConv_ = nil;
96 convUid_ = "";
97 convModel_ = nil;
98
99 QObject::disconnect(modelSortedSignal_);
100 QObject::disconnect(filterChangedSignal_);
101 QObject::disconnect(interactionStatusUpdatedSignal_);
102 QObject::disconnect(newInteractionSignal_);
103}
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400104
Anthony Léonard2382b562017-12-13 15:51:28 -0500105-(const lrc::api::conversation::Info*) getCurrentConversation
106{
107 if (convModel_ == nil || convUid_.empty())
108 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400109
Anthony Léonard2382b562017-12-13 15:51:28 -0500110 if (cachedConv_ != nil)
111 return cachedConv_;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400112
Anthony Léonard6f819752018-01-05 09:53:40 -0500113 auto it = getConversationFromUid(convUid_, *convModel_);
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400114 if (it != convModel_->allFilteredConversations().end())
Anthony Léonard2382b562017-12-13 15:51:28 -0500115 cachedConv_ = &(*it);
116
117 return cachedConv_;
118}
119
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400120-(void) reloadConversationForMessage:(uint64_t) uid shouldUpdateHeight:(bool)update {
121 auto* conv = [self getCurrentConversation];
122
123 if (conv == nil)
124 return;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400125 auto it = conv->interactions.find(uid);
126 if (it == conv->interactions.end()) {
127 return;
128 }
129 auto itIndex = distance(conv->interactions.begin(),it);
130 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:itIndex];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400131 //reload previous message to update bubbleview
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400132 if (itIndex > 0) {
133 auto previousIt = it;
134 previousIt--;
135 auto previousInteraction = previousIt->second;
136 if (previousInteraction.type == lrc::api::interaction::Type::TEXT) {
137 NSRange range = NSMakeRange(itIndex - 1, 2);
138 indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
139 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400140 }
141 if (update) {
142 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
143 }
144 [conversationView reloadDataForRowIndexes: indexSet
145 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400146 CGRect visibleRect = [conversationView enclosingScrollView].contentView.visibleRect;
147 NSRange range = [conversationView rowsInRect:visibleRect];
148 NSIndexSet* visibleIndexes = [NSIndexSet indexSetWithIndexesInRange:range];
149 NSUInteger lastvisibleRow = [visibleIndexes lastIndex];
150 if (([conversationView numberOfRows] > 0) &&
151 lastvisibleRow == ([conversationView numberOfRows] -1)) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400152 [conversationView scrollToEndOfDocument:nil];
153 }
154}
155
156-(void) reloadConversationForMessage:(uint64_t) uid shouldUpdateHeight:(bool)update updateConversation:(bool) updateConversation {
157 auto* conv = [self getCurrentConversation];
158
159 if (conv == nil)
160 return;
161 auto it = distance(conv->interactions.begin(),conv->interactions.find(uid));
162 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:it];
163 //reload previous message to update bubbleview
164 if (it > 0) {
165 NSRange range = NSMakeRange(it - 1, it);
166 indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
167 }
168 if (update) {
169 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
170 }
171 [conversationView reloadDataForRowIndexes: indexSet
172 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
173 if (update) {
174 [conversationView scrollToEndOfDocument:nil];
175 }
176}
177
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500178-(void)setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel *)model
Anthony Léonard2382b562017-12-13 15:51:28 -0500179{
180 if (convUid_ == convUid && convModel_ == model)
181 return;
182
183 cachedConv_ = nil;
184 convUid_ = convUid;
185 convModel_ = model;
186
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500187 // Signal triggered when messages are received or their status updated
188 QObject::disconnect(newInteractionSignal_);
189 QObject::disconnect(interactionStatusUpdatedSignal_);
190 newInteractionSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::newInteraction,
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400191 [self](const std::string& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
192 if (uid != convUid_)
193 return;
194 cachedConv_ = nil;
195 [conversationView noteNumberOfRowsChanged];
196 [self reloadConversationForMessage:interactionId shouldUpdateHeight:YES];
197 });
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500198 interactionStatusUpdatedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::interactionStatusUpdated,
199 [self](const std::string& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
200 if (uid != convUid_)
201 return;
202 cachedConv_ = nil;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400203 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
204 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
205 convModel_->refreshFilter();
206 }
207 [self reloadConversationForMessage:interactionId shouldUpdateHeight:YES];
208 //accept incoming transfer
209 if (interaction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER &&
210 (interaction.status == lrc::api::interaction::Status::TRANSFER_AWAITING_HOST ||
211 interaction.status == lrc::api::interaction::Status::TRANSFER_CREATED)) {
212 lrc::api::datatransfer::Info info = {};
213 convModel_->getTransferInfo(interactionId, info);
214 double convertData = static_cast<double>(info.totalSize);
215 NSString* pathUrl = @(info.displayName.c_str());
216
217 NSString* fileExtension = pathUrl.pathExtension;
218
219 CFStringRef utiType = UTTypeCreatePreferredIdentifierForTag(
220 kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
221
222 bool isImage = UTTypeConformsTo(utiType, kUTTypeImage);
223 if( convertData <= 10485760 && isImage) {
224 [self acceptFile:interactionId];
225 }
226 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500227 });
Anthony Léonard2382b562017-12-13 15:51:28 -0500228
229 // Signals tracking changes in conversation list, we need them as cached conversation can be invalid
230 // after a reordering.
231 QObject::disconnect(modelSortedSignal_);
232 QObject::disconnect(filterChangedSignal_);
233 modelSortedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelSorted,
234 [self](){
235 cachedConv_ = nil;
236 });
237 filterChangedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::filterChanged,
238 [self](){
239 cachedConv_ = nil;
240 });
Anthony Léonard2382b562017-12-13 15:51:28 -0500241 [conversationView reloadData];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400242 [conversationView scrollToEndOfDocument:nil];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400243}
244
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400245#pragma mark - configure cells
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400246
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400247-(NSTableCellView*) makeGenericInteractionViewForTableView:(NSTableView*)tableView withText:(NSString*)text andTime:(NSString*) time
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500248{
249 NSTableCellView* result = [tableView makeViewWithIdentifier:@"GenericInteractionView" owner:self];
250 NSTextField* textField = [result viewWithTag:GENERIC_INT_TEXT_TAG];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400251 NSTextField* timeField = [result viewWithTag:GENERIC_INT_TIME_TAG];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500252
253 // TODO: Fix symbol in LRC
254 NSString* fixedString = [text stringByReplacingOccurrencesOfString:@"🕽" withString:@"📞"];
255 [textField setStringValue:fixedString];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400256 [timeField setStringValue:time];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500257
258 return result;
259}
260
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400261-(NSTableCellView*) configureViewforTransfer:(lrc::api::interaction::Info)interaction interactionID: (uint64_t) interactionID tableView:(NSTableView*)tableView
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500262{
263 IMTableCellView* result;
264
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400265 auto type = interaction.type;
266 auto status = interaction.status;
267
268 NSString* fileName = @"incoming file";
269
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500270 // First, view is created
271 if (type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER) {
272 switch (status) {
273 case lrc::api::interaction::Status::TRANSFER_CREATED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400274 case lrc::api::interaction::Status::TRANSFER_AWAITING_HOST: {
275 result = [tableView makeViewWithIdentifier:@"LeftIncomingFileView" owner: conversationView];
276 [result.acceptButton setAction:@selector(acceptIncomingFile:)];
277 [result.acceptButton setTarget:self];
278 [result.declineButton setAction:@selector(declineIncomingFile:)];
279 [result.declineButton setTarget:self];
280 break;}
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500281 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400282 case lrc::api::interaction::Status::TRANSFER_ONGOING: {
283 result = [tableView makeViewWithIdentifier:@"LeftOngoingFileView" owner:conversationView];
284 [result.progressIndicator startAnimation:conversationView];
285 [result.declineButton setAction:@selector(declineIncomingFile:)];
286 [result.declineButton setTarget:self];
287 break;}
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500288 case lrc::api::interaction::Status::TRANSFER_FINISHED:
289 case lrc::api::interaction::Status::TRANSFER_CANCELED:
290 case lrc::api::interaction::Status::TRANSFER_ERROR:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400291 result = [tableView makeViewWithIdentifier:@"LeftFinishedFileView" owner:conversationView];
292 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500293 }
294 } else if (type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400295 NSString* fileName = @"sent file";
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500296 switch (status) {
297 case lrc::api::interaction::Status::TRANSFER_CREATED:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500298 case lrc::api::interaction::Status::TRANSFER_ONGOING:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400299 case lrc::api::interaction::Status::TRANSFER_AWAITING_PEER:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500300 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400301 result = [tableView makeViewWithIdentifier:@"RightOngoingFileView" owner:conversationView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500302 [result.progressIndicator startAnimation:nil];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400303 [result.declineButton setAction:@selector(declineIncomingFile:)];
304 [result.declineButton setTarget:self];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500305 break;
306 case lrc::api::interaction::Status::TRANSFER_FINISHED:
307 case lrc::api::interaction::Status::TRANSFER_CANCELED:
308 case lrc::api::interaction::Status::TRANSFER_ERROR:
Olivier Soldanoe521a182018-02-26 16:55:19 -0500309 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400310 result = [tableView makeViewWithIdentifier:@"RightFinishedFileView" owner:conversationView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500311 }
312 }
313
314 // Then status label is updated if needed
315 switch (status) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400316 [result.statusLabel setTextColor:[NSColor textColor]];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500317 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400318 [result.statusLabel setTextColor:[NSColor greenSuccessColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500319 [result.statusLabel setStringValue:NSLocalizedString(@"Success", @"File transfer successful label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500320 break;
321 case lrc::api::interaction::Status::TRANSFER_CANCELED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400322 [result.statusLabel setTextColor:[NSColor orangeColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500323 [result.statusLabel setStringValue:NSLocalizedString(@"Canceled", @"File transfer canceled label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500324 break;
325 case lrc::api::interaction::Status::TRANSFER_ERROR:
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400326 [result.statusLabel setTextColor:[NSColor errorTransferColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500327 [result.statusLabel setStringValue:NSLocalizedString(@"Failed", @"File transfer failed label")];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500328 break;
329 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400330 [result.statusLabel setTextColor:[NSColor textColor]];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500331 [result.statusLabel setStringValue:NSLocalizedString(@"Unjoinable", @"File transfer peer unjoinable label")];
332 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500333 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400334 result.transferedImage.image = nil;
335 [result.msgBackground setHidden:NO];
336 [result invalidateImageConstraints];
337 NSString* name = @(interaction.body.c_str());
338 if (name.length > 0) {
339 if (([name rangeOfString:@"/"].location != NSNotFound)) {
340 NSArray *listItems = [name componentsSeparatedByString:@"/"];
341 NSString* name1 = listItems.lastObject;
342 fileName = name1;
343 } else {
344 fileName = name;
345 }
346 }
347 result.transferedFileName.stringValue = fileName;
348 if (status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
349 NSImage* image = [self getImageForFilePath:name];
350 if (([name rangeOfString:@"/"].location == NSNotFound)) {
351 image = [self getImageForFilePath:[self getDataTransferPath:interactionID]];
352 }
353 if(image != nil) {
354 result.transferedImage.image = [image roundCorners:14];
355 [result updateImageConstraint:image.size.width andHeight:image.size.height];
356 }
357 }
358 [result setupForInteraction:interactionID];
359 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
360 NSString* timeString = [self timeForMessage: msgTime];
361 result.timeLabel.stringValue = timeString;
362 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
363 if (!isOutgoing) {
364 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
365 auto* conv = [self getCurrentConversation];
366 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
367 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500368 return result;
369}
370
Anthony Léonard2382b562017-12-13 15:51:28 -0500371#pragma mark - NSTableViewDelegate methods
372- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400373{
374 return YES;
375}
376
Anthony Léonard2382b562017-12-13 15:51:28 -0500377- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400378{
Anthony Léonard2382b562017-12-13 15:51:28 -0500379 return NO;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400380}
381
Anthony Léonard2382b562017-12-13 15:51:28 -0500382- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400383{
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400384
Anthony Léonard2382b562017-12-13 15:51:28 -0500385 auto* conv = [self getCurrentConversation];
386
387 if (conv == nil)
388 return nil;
389
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500390 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500391
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500392 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500393
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400394 IMTableCellView* result;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400395 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500396 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
397
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500398 switch (interaction.type) {
399 case lrc::api::interaction::Type::TEXT:
400 if (isOutgoing) {
401 result = [tableView makeViewWithIdentifier:@"RightMessageView" owner:self];
402 } else {
403 result = [tableView makeViewWithIdentifier:@"LeftMessageView" owner:self];
404 }
405 break;
406 case lrc::api::interaction::Type::INCOMING_DATA_TRANSFER:
407 case lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400408 return [self configureViewforTransfer:interaction interactionID: it->first tableView:tableView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500409 break;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500410 case lrc::api::interaction::Type::CONTACT:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400411 case lrc::api::interaction::Type::CALL: {
412 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
413 NSString* timeString = [self timeForMessage: msgTime];
414 return [self makeGenericInteractionViewForTableView:tableView withText:@(interaction.body.c_str()) andTime:timeString];
415 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500416 default: // If interaction is not of a known type
417 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400418 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400419 MessageSequencing sequence = [self computeSequencingFor:row];
420 BubbleType type = SINGLE;
421 if (sequence == FIRST_WITHOUT_TIME || sequence == FIRST_WITH_TIME) {
422 type = FIRST;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400423 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400424 if (sequence == MIDDLE_IN_SEQUENCE) {
425 type = MIDDLE;
426 }
427 if (sequence == LAST_IN_SEQUENCE) {
428 type = LAST;
429 }
430 result.msgBackground.type = type;
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400431 bool sendingFail = false;
432 [result.messageStatus setHidden:YES];
433 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
434 if (interaction.status == lrc::api::interaction::Status::SENDING) {
435 [result.messageStatus setHidden:NO];
436 [result.sendingMessageIndicator startAnimation:nil];
437 [result.messageFailed setHidden:YES];
438 } else if (interaction.status == lrc::api::interaction::Status::FAILED) {
439 [result.messageStatus setHidden:NO];
440 [result.sendingMessageIndicator setHidden:YES];
441 [result.messageFailed setHidden:NO];
442 sendingFail = true;
443 }
444 }
445 [result setupForInteraction:it->first isFailed: sendingFail];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400446 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400447 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400448 [result.msgBackground setNeedsDisplay:YES];
449 [result setNeedsDisplay:YES];
450 [result.timeBox setNeedsDisplay:YES];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400451
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400452 NSString *text = @(interaction.body.c_str());
453 text = [text removeEmptyLinesAtBorders];
454
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400455 NSMutableAttributedString* msgAttString =
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400456 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:text]
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400457 attributes:[self messageAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400458
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400459 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400460
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400461 [result updateMessageConstraint:messageSize.width andHeight:messageSize.height timeIsVisible:shouldDisplayTime isTopPadding: shouldApplyPadding];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400462 [[result.msgView textStorage] appendAttributedString:msgAttString];
463 [result.msgView checkTextInDocument:nil];
Anthony Léonard2382b562017-12-13 15:51:28 -0500464
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400465 NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
466 NSArray *matches = [linkDetector matchesInString:result.msgView.string options:0 range:NSMakeRange(0, result.msgView.string.length)];
467
468 [result.msgView.textStorage beginEditing];
469
470 for (NSTextCheckingResult *match in matches) {
471 if (!match.URL) continue;
472
473 NSDictionary *linkAttributes = @{
474 NSLinkAttributeName: match.URL,
475 };
476 [result.msgView.textStorage addAttributes:linkAttributes range:match.range];
477 }
478
479 [result.msgView.textStorage endEditing];
480
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400481 if (shouldDisplayTime) {
482 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
483 NSString* timeString = [self timeForMessage: msgTime];
484 result.timeLabel.stringValue = timeString;
Anthony Léonard64e19672018-01-18 16:40:34 -0500485 }
486
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400487 bool shouldDisplayAvatar = (sequence != MIDDLE_IN_SEQUENCE && sequence != FIRST_WITHOUT_TIME
488 && sequence != FIRST_WITH_TIME) ? YES : NO;
489 [result.photoView setHidden:!shouldDisplayAvatar];
490 if (!isOutgoing && shouldDisplayAvatar) {
491 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
492 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
493 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400494 return result;
495}
496
Anthony Léonard2382b562017-12-13 15:51:28 -0500497- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400498{
Anthony Léonard2382b562017-12-13 15:51:28 -0500499 double someWidth = tableView.frame.size.width * 0.7;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400500
Anthony Léonard2382b562017-12-13 15:51:28 -0500501 auto* conv = [self getCurrentConversation];
502
503 if (conv == nil)
504 return 0;
505
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500506 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500507
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500508 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500509
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400510 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500511
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400512 MessageSequencing sequence = [self computeSequencingFor:row];
513
514 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
515
516
517 if(interaction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER || interaction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER) {
518
519 if( interaction.status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
520 NSString* name = @(interaction.body.c_str());
521 NSImage* image = [self getImageForFilePath:name];
522 if (([name rangeOfString:@"/"].location == NSNotFound)) {
523 image = [self getImageForFilePath:[self getDataTransferPath:it->first]];
524 }
525 if (image != nil) {
526 return image.size.height + TIME_BOX_HEIGHT;
527 }
528 }
529 return BUBBLE_HEIGHT_FOR_TRANSFERED_FILE + TIME_BOX_HEIGHT;
530 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500531
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500532 if(interaction.type == lrc::api::interaction::Type::CONTACT || interaction.type == lrc::api::interaction::Type::CALL)
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400533 return GENERIC_CELL_HEIGHT;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500534
Anthony Léonard2382b562017-12-13 15:51:28 -0500535 // TODO Implement interactions other than messages
536 if(interaction.type != lrc::api::interaction::Type::TEXT) {
537 return 0;
538 }
539
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400540 NSString *text = @(interaction.body.c_str());
541 text = [text removeEmptyLinesAtBorders];
542
543 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400544 CGFloat singleLignMessageHeight = 15;
545
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400546 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
547
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400548 if (shouldDisplayTime) {
549 return MAX(messageSize.height + TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2,
550 TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2 + singleLignMessageHeight);
551 }
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400552 if(shouldApplyPadding) {
553 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2 + 15,
554 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2 + 15);
555 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400556 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2,
557 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2);
558}
559
560#pragma mark - message view parameters
561
562-(NSString *) getDataTransferPath:(uint64_t)interactionId {
563 lrc::api::datatransfer::Info info = {};
564 convModel_->getTransferInfo(interactionId, info);
565 double convertData = static_cast<double>(info.totalSize);
566 return @(info.path.c_str());
567}
568
569-(NSImage*) getImageForFilePath: (NSString *) path {
570 if (path.length <= 0) {return nil;}
571 if (![[NSFileManager defaultManager] fileExistsAtPath: path]) {return nil;}
572 NSImage* transferedImage = [[NSImage alloc] initWithContentsOfFile: path];
573 if(transferedImage != nil) {
574 return [transferedImage imageResizeInsideMax: MAX_TRANSFERED_IMAGE_SIZE];
575 }
576 return nil;
577}
578
579-(CGSize) sizeFor:(NSString *) message maxWidth:(CGFloat) width {
580 CGFloat horizaontalMargin = 6;
Anthony Léonard2382b562017-12-13 15:51:28 -0500581 NSMutableAttributedString* msgAttString =
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400582 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@", message]
Anthony Léonard2382b562017-12-13 15:51:28 -0500583 attributes:[self messageAttributes]];
584
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400585 CGFloat finalWidth = MIN(msgAttString.size.width + horizaontalMargin * 2, width);
586 NSRect frame = NSMakeRect(0, 0, finalWidth, msgAttString.size.height);
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400587 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400588 [[tv textStorage] setAttributedString:msgAttString];
589 [tv sizeToFit];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400590 return tv.frame.size;
591}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400592
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400593-(MessageSequencing) computeSequencingFor:(NSInteger) row {
594 auto* conv = [self getCurrentConversation];
595 if (conv == nil)
596 return SINGLE_WITHOUT_TIME;
597 auto it = conv->interactions.begin();
598 std::advance(it, row);
599 auto interaction = it->second;
600 if (interaction.type != lrc::api::interaction::Type::TEXT) {
601 return SINGLE_WITH_TIME;
602 }
603 if (row == 0) {
604 if (it == conv->interactions.end()) {
605 return SINGLE_WITH_TIME;
606 }
607 auto nextIt = it;
608 nextIt++;
609 auto nextInteraction = nextIt->second;
610 if ([self sequenceChangedFrom:interaction to: nextInteraction]) {
611 return SINGLE_WITH_TIME;
612 }
613 return FIRST_WITH_TIME;
614 }
615
616 if (row == conversationView.numberOfRows - 1) {
617 if(it == conv->interactions.begin()) {
618 return SINGLE_WITH_TIME;
619 }
620 auto previousIt = it;
621 previousIt--;
622 auto previousInteraction = previousIt->second;
623 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
624 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
625 if (!timeChanged && !authorChanged) {
626 return LAST_IN_SEQUENCE;
627 }
628 if (!timeChanged && authorChanged) {
629 return SINGLE_WITHOUT_TIME;
630 }
631 return SINGLE_WITH_TIME;
632 }
633 if(it == conv->interactions.begin() || it == conv->interactions.end()) {
634 return SINGLE_WITH_TIME;
635 }
636 auto previousIt = it;
637 previousIt--;
638 auto previousInteraction = previousIt->second;
639 auto nextIt = it;
640 nextIt++;
641 auto nextInteraction = nextIt->second;
642
643 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
644 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
645 bool sequenceWillChange = [self sequenceChangedFrom:interaction to: nextInteraction];
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400646 if (previousInteraction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER ||
647 previousInteraction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER) {
648 if(!sequenceWillChange) {
649 return FIRST_WITH_TIME;
650 }
651 return SINGLE_WITH_TIME;
652 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400653 if (!sequenceWillChange) {
654 if (!timeChanged && !authorChanged) {
655 return MIDDLE_IN_SEQUENCE;
656 }
657 if (timeChanged) {
658 return FIRST_WITH_TIME;
659 }
660 return FIRST_WITHOUT_TIME;
661 } if (!timeChanged && !authorChanged) {
662 return LAST_IN_SEQUENCE;
663 } if (timeChanged) {
664 return SINGLE_WITH_TIME;
665 }
666 return SINGLE_WITHOUT_TIME;
667}
668
669-(bool) sequenceChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
670 return ([self sequenceTimeChangedFrom:firstInteraction to:secondInteraction] || [self sequenceAuthorChangedFrom:firstInteraction to:secondInteraction]);
671}
672
673-(bool) sequenceTimeChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
674 bool timeChanged = NO;
675 NSDate* firstMessageTime = [NSDate dateWithTimeIntervalSince1970:firstInteraction.timestamp];
676 NSDate* secondMessageTime = [NSDate dateWithTimeIntervalSince1970:secondInteraction.timestamp];
677 bool hourComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitHour];
678 bool minutComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitMinute];
679 if(hourComp != NSOrderedSame || minutComp != NSOrderedSame) {
680 timeChanged = YES;
681 }
682 return timeChanged;
683}
684
685-(bool) sequenceAuthorChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
686 bool authorChanged = YES;
687 bool isOutgoing = lrc::api::interaction::isOutgoing(firstInteraction);
688 if ((secondInteraction.type == lrc::api::interaction::Type::TEXT) && (isOutgoing == lrc::api::interaction::isOutgoing(secondInteraction))) {
689 authorChanged = NO;
690 }
691 return authorChanged;
692}
693
694-(NSString *)timeForMessage:(NSDate*) msgTime {
695 NSDate *today = [NSDate date];
696 NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
697 if ([[NSCalendar currentCalendar] compareDate:today
698 toDate:msgTime
699 toUnitGranularity:NSCalendarUnitYear]!= NSOrderedSame) {
700 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle];
701 }
702
703 if ([[NSCalendar currentCalendar] compareDate:today
704 toDate:msgTime
705 toUnitGranularity:NSCalendarUnitDay]!= NSOrderedSame ||
706 [[NSCalendar currentCalendar] compareDate:today
707 toDate:msgTime
708 toUnitGranularity:NSCalendarUnitMonth]!= NSOrderedSame) {
709 [dateFormatter setDateFormat:@"MMM dd, HH:mm"];
710 return [dateFormatter stringFromDate:msgTime];
711 }
712
713 [dateFormatter setDateFormat:@"HH:mm"];
714 return [dateFormatter stringFromDate:msgTime];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400715}
716
Anthony Léonard2382b562017-12-13 15:51:28 -0500717#pragma mark - NSTableViewDataSource
718
719- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
720{
721 auto* conv = [self getCurrentConversation];
722
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500723 if (conv)
724 return conv->interactions.size();
725 else
726 return 0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500727}
728
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400729#pragma mark - Text formatting
730
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400731- (NSMutableDictionary*) messageAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400732{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400733 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400734 attrs[NSForegroundColorAttributeName] = [NSColor labelColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400735 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400736 return attrs;
737}
738
739- (NSParagraphStyle*) paragraphStyle
740{
741 /*
742 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
743 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
744 to copy, we use the default one.
745
746 The default values supplied by the default NSParagraphStyle are:
747 Alignment NSNaturalTextAlignment
748 Tab stops 12 left-aligned tabs, spaced by 28.0 points
749 Line break mode NSLineBreakByWordWrapping
750 All others 0.0
751 */
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400752 NSMutableParagraphStyle* aMutableParagraphStyle =
753 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
754 [aMutableParagraphStyle setHeadIndent:1.0];
755 [aMutableParagraphStyle setFirstLineHeadIndent:1.0];
756 return aMutableParagraphStyle;
757}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400758
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400759- (void)acceptFile:(uint64_t)interactionID {
760 NSURL *downloadsURL = [[NSFileManager defaultManager]
761 URLForDirectory:NSDownloadsDirectory
762 inDomain:NSUserDomainMask appropriateForURL:nil
763 create:YES error:nil];
764 auto& inter = [self getCurrentConversation]->interactions.find(interactionID)->second;
765 if (convModel_ && !convUid_.empty()) {
766 NSURL *bUrl = [downloadsURL URLByAppendingPathComponent:@(inter.body.c_str())];
767 const char* fullPath = [bUrl fileSystemRepresentation];
768 convModel_->acceptTransfer(convUid_, interactionID, fullPath);
769 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400770}
771
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500772#pragma mark - Actions
773
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400774- (void)acceptIncomingFile:(id)sender {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500775 auto interId = [(IMTableCellView*)[[sender superview] superview] interaction];
776 auto& inter = [self getCurrentConversation]->interactions.find(interId)->second;
777 if (convModel_ && !convUid_.empty()) {
778 NSSavePanel* filePicker = [NSSavePanel savePanel];
779 [filePicker setNameFieldStringValue:@(inter.body.c_str())];
780
781 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
782 const char* fullPath = [[filePicker URL] fileSystemRepresentation];
783 convModel_->acceptTransfer(convUid_, interId, fullPath);
784 }
785 }
786}
787
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400788- (void)declineIncomingFile:(id)sender {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500789 auto inter = [(IMTableCellView*)[[sender superview] superview] interaction];
790 if (convModel_ && !convUid_.empty()) {
791 convModel_->cancelTransfer(convUid_, inter);
792 }
793}
794
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400795@end