blob: a05b8c5e71c3d89c60886f54857efa6cc8eeb80e [file] [log] [blame]
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04001/*
Anthony Léonarde7d62ed2018-01-25 10:51:47 -05002 * Copyright (C) 2015-2018 Savoir-faire Linux Inc.
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04003 * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
Anthony Léonard2382b562017-12-13 15:51:28 -05004 * Anthony Léonard <anthony.leonard@savoirfairelinux.com>
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04005 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21#import <QItemSelectionModel>
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"
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040033#import "INDSequentialTextSelectionManager.h"
Anthony Léonard2382b562017-12-13 15:51:28 -050034#import "delegates/ImageManipulationDelegate.h"
Anthony Léonard6f819752018-01-05 09:53:40 -050035#import "utils.h"
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040036#import "views/NSColor+RingTheme.h"
37
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040038
Anthony Léonard2382b562017-12-13 15:51:28 -050039@interface MessagesVC () <NSTableViewDelegate, NSTableViewDataSource> {
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040040
Anthony Léonard2382b562017-12-13 15:51:28 -050041 __unsafe_unretained IBOutlet NSTableView* conversationView;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040042
Anthony Léonard2382b562017-12-13 15:51:28 -050043 std::string convUid_;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050044 lrc::api::ConversationModel* convModel_;
Anthony Léonard2382b562017-12-13 15:51:28 -050045 const lrc::api::conversation::Info* cachedConv_;
46
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050047 QMetaObject::Connection newInteractionSignal_;
Anthony Léonard2382b562017-12-13 15:51:28 -050048
49 // Both are needed to invalidate cached conversation as pointer
50 // may not be referencing the same conversation anymore
51 QMetaObject::Connection modelSortedSignal_;
52 QMetaObject::Connection filterChangedSignal_;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050053 QMetaObject::Connection interactionStatusUpdatedSignal_;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040054}
55
56@property (nonatomic, strong, readonly) INDSequentialTextSelectionManager* selectionManager;
57
58@end
59
Anthony Léonardf2bb17d2018-02-15 17:18:09 -050060// Tags for view
61NSInteger const GENERIC_INT_TEXT_TAG = 100;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040062NSInteger const GENERIC_INT_TIME_TAG = 200;
63
64// views size
65CGFloat const GENERIC_CELL_HEIGHT = 60;
66CGFloat const TIME_BOX_HEIGHT = 34;
67CGFloat const MESSAGE_TEXT_PADDING = 10;
68CGFloat const MAX_TRANSFERED_IMAGE_SIZE = 250;
69CGFloat const BUBBLE_HEIGHT_FOR_TRANSFERED_FILE = 87;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -050070
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040071@implementation MessagesVC
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040072
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040073//MessageBuble type
74typedef NS_ENUM(NSInteger, MessageSequencing) {
75 SINGLE_WITH_TIME = 0,
76 SINGLE_WITHOUT_TIME = 1,
77 FIRST_WITH_TIME = 2,
78 FIRST_WITHOUT_TIME = 3,
79 MIDDLE_IN_SEQUENCE = 5,
80 LAST_IN_SEQUENCE = 6,
81};
82
83- (void)awakeFromNib
84{
85 NSNib *cellNib = [[NSNib alloc] initWithNibNamed:@"MessageCells" bundle:nil];
86 [conversationView registerNib:cellNib forIdentifier:@"LeftIncomingFileView"];
87 [conversationView registerNib:cellNib forIdentifier:@"LeftOngoingFileView"];
88 [conversationView registerNib:cellNib forIdentifier:@"LeftFinishedFileView"];
89 [conversationView registerNib:cellNib forIdentifier:@"RightOngoingFileView"];
90 [conversationView registerNib:cellNib forIdentifier:@"RightFinishedFileView"];
91}
92
Anthony Léonard2382b562017-12-13 15:51:28 -050093-(const lrc::api::conversation::Info*) getCurrentConversation
94{
95 if (convModel_ == nil || convUid_.empty())
96 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040097
Anthony Léonard2382b562017-12-13 15:51:28 -050098 if (cachedConv_ != nil)
99 return cachedConv_;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400100
Anthony Léonard6f819752018-01-05 09:53:40 -0500101 auto it = getConversationFromUid(convUid_, *convModel_);
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400102 if (it != convModel_->allFilteredConversations().end())
Anthony Léonard2382b562017-12-13 15:51:28 -0500103 cachedConv_ = &(*it);
104
105 return cachedConv_;
106}
107
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400108-(void) reloadConversationForMessage:(uint64_t) uid shouldUpdateHeight:(bool)update {
109 auto* conv = [self getCurrentConversation];
110
111 if (conv == nil)
112 return;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400113 auto it = conv->interactions.find(uid);
114 if (it == conv->interactions.end()) {
115 return;
116 }
117 auto itIndex = distance(conv->interactions.begin(),it);
118 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:itIndex];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400119 //reload previous message to update bubbleview
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400120 if (itIndex > 0) {
121 auto previousIt = it;
122 previousIt--;
123 auto previousInteraction = previousIt->second;
124 if (previousInteraction.type == lrc::api::interaction::Type::TEXT) {
125 NSRange range = NSMakeRange(itIndex - 1, 2);
126 indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
127 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400128 }
129 if (update) {
130 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
131 }
132 [conversationView reloadDataForRowIndexes: indexSet
133 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400134 CGRect visibleRect = [conversationView enclosingScrollView].contentView.visibleRect;
135 NSRange range = [conversationView rowsInRect:visibleRect];
136 NSIndexSet* visibleIndexes = [NSIndexSet indexSetWithIndexesInRange:range];
137 NSUInteger lastvisibleRow = [visibleIndexes lastIndex];
138 if (([conversationView numberOfRows] > 0) &&
139 lastvisibleRow == ([conversationView numberOfRows] -1)) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400140 [conversationView scrollToEndOfDocument:nil];
141 }
142}
143
144-(void) reloadConversationForMessage:(uint64_t) uid shouldUpdateHeight:(bool)update updateConversation:(bool) updateConversation {
145 auto* conv = [self getCurrentConversation];
146
147 if (conv == nil)
148 return;
149 auto it = distance(conv->interactions.begin(),conv->interactions.find(uid));
150 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:it];
151 //reload previous message to update bubbleview
152 if (it > 0) {
153 NSRange range = NSMakeRange(it - 1, it);
154 indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
155 }
156 if (update) {
157 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
158 }
159 [conversationView reloadDataForRowIndexes: indexSet
160 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
161 if (update) {
162 [conversationView scrollToEndOfDocument:nil];
163 }
164}
165
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500166-(void)setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel *)model
Anthony Léonard2382b562017-12-13 15:51:28 -0500167{
168 if (convUid_ == convUid && convModel_ == model)
169 return;
170
171 cachedConv_ = nil;
172 convUid_ = convUid;
173 convModel_ = model;
174
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500175 // Signal triggered when messages are received or their status updated
176 QObject::disconnect(newInteractionSignal_);
177 QObject::disconnect(interactionStatusUpdatedSignal_);
178 newInteractionSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::newInteraction,
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400179 [self](const std::string& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
180 if (uid != convUid_)
181 return;
182 cachedConv_ = nil;
183 [conversationView noteNumberOfRowsChanged];
184 [self reloadConversationForMessage:interactionId shouldUpdateHeight:YES];
185 });
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500186 interactionStatusUpdatedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::interactionStatusUpdated,
187 [self](const std::string& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
188 if (uid != convUid_)
189 return;
190 cachedConv_ = nil;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400191 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
192 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
193 convModel_->refreshFilter();
194 }
195 [self reloadConversationForMessage:interactionId shouldUpdateHeight:YES];
196 //accept incoming transfer
197 if (interaction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER &&
198 (interaction.status == lrc::api::interaction::Status::TRANSFER_AWAITING_HOST ||
199 interaction.status == lrc::api::interaction::Status::TRANSFER_CREATED)) {
200 lrc::api::datatransfer::Info info = {};
201 convModel_->getTransferInfo(interactionId, info);
202 double convertData = static_cast<double>(info.totalSize);
203 NSString* pathUrl = @(info.displayName.c_str());
204
205 NSString* fileExtension = pathUrl.pathExtension;
206
207 CFStringRef utiType = UTTypeCreatePreferredIdentifierForTag(
208 kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
209
210 bool isImage = UTTypeConformsTo(utiType, kUTTypeImage);
211 if( convertData <= 10485760 && isImage) {
212 [self acceptFile:interactionId];
213 }
214 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500215 });
Anthony Léonard2382b562017-12-13 15:51:28 -0500216
217 // Signals tracking changes in conversation list, we need them as cached conversation can be invalid
218 // after a reordering.
219 QObject::disconnect(modelSortedSignal_);
220 QObject::disconnect(filterChangedSignal_);
221 modelSortedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelSorted,
222 [self](){
223 cachedConv_ = nil;
224 });
225 filterChangedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::filterChanged,
226 [self](){
227 cachedConv_ = nil;
228 });
Anthony Léonard2382b562017-12-13 15:51:28 -0500229 [conversationView reloadData];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400230 [conversationView scrollToEndOfDocument:nil];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400231}
232
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400233#pragma mark - configure cells
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400234
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400235-(NSTableCellView*) makeGenericInteractionViewForTableView:(NSTableView*)tableView withText:(NSString*)text andTime:(NSString*) time
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500236{
237 NSTableCellView* result = [tableView makeViewWithIdentifier:@"GenericInteractionView" owner:self];
238 NSTextField* textField = [result viewWithTag:GENERIC_INT_TEXT_TAG];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400239 NSTextField* timeField = [result viewWithTag:GENERIC_INT_TIME_TAG];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500240
241 // TODO: Fix symbol in LRC
242 NSString* fixedString = [text stringByReplacingOccurrencesOfString:@"🕽" withString:@"📞"];
243 [textField setStringValue:fixedString];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400244 [timeField setStringValue:time];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500245
246 return result;
247}
248
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400249-(NSTableCellView*) configureViewforTransfer:(lrc::api::interaction::Info)interaction interactionID: (uint64_t) interactionID tableView:(NSTableView*)tableView
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500250{
251 IMTableCellView* result;
252
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400253 auto type = interaction.type;
254 auto status = interaction.status;
255
256 NSString* fileName = @"incoming file";
257
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500258 // First, view is created
259 if (type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER) {
260 switch (status) {
261 case lrc::api::interaction::Status::TRANSFER_CREATED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400262 case lrc::api::interaction::Status::TRANSFER_AWAITING_HOST: {
263 result = [tableView makeViewWithIdentifier:@"LeftIncomingFileView" owner: conversationView];
264 [result.acceptButton setAction:@selector(acceptIncomingFile:)];
265 [result.acceptButton setTarget:self];
266 [result.declineButton setAction:@selector(declineIncomingFile:)];
267 [result.declineButton setTarget:self];
268 break;}
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500269 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400270 case lrc::api::interaction::Status::TRANSFER_ONGOING: {
271 result = [tableView makeViewWithIdentifier:@"LeftOngoingFileView" owner:conversationView];
272 [result.progressIndicator startAnimation:conversationView];
273 [result.declineButton setAction:@selector(declineIncomingFile:)];
274 [result.declineButton setTarget:self];
275 break;}
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500276 case lrc::api::interaction::Status::TRANSFER_FINISHED:
277 case lrc::api::interaction::Status::TRANSFER_CANCELED:
278 case lrc::api::interaction::Status::TRANSFER_ERROR:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400279 result = [tableView makeViewWithIdentifier:@"LeftFinishedFileView" owner:conversationView];
280 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500281 }
282 } else if (type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400283 NSString* fileName = @"sent file";
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500284 switch (status) {
285 case lrc::api::interaction::Status::TRANSFER_CREATED:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500286 case lrc::api::interaction::Status::TRANSFER_ONGOING:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400287 case lrc::api::interaction::Status::TRANSFER_AWAITING_PEER:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500288 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400289 result = [tableView makeViewWithIdentifier:@"RightOngoingFileView" owner:conversationView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500290 [result.progressIndicator startAnimation:nil];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400291 [result.declineButton setAction:@selector(declineIncomingFile:)];
292 [result.declineButton setTarget:self];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500293 break;
294 case lrc::api::interaction::Status::TRANSFER_FINISHED:
295 case lrc::api::interaction::Status::TRANSFER_CANCELED:
296 case lrc::api::interaction::Status::TRANSFER_ERROR:
Olivier Soldanoe521a182018-02-26 16:55:19 -0500297 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400298 result = [tableView makeViewWithIdentifier:@"RightFinishedFileView" owner:conversationView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500299 }
300 }
301
302 // Then status label is updated if needed
303 switch (status) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400304 [result.statusLabel setTextColor:[NSColor textColor]];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500305 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400306 [result.statusLabel setTextColor:[NSColor greenColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500307 [result.statusLabel setStringValue:NSLocalizedString(@"Success", @"File transfer successful label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500308 break;
309 case lrc::api::interaction::Status::TRANSFER_CANCELED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400310 [result.statusLabel setTextColor:[NSColor orangeColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500311 [result.statusLabel setStringValue:NSLocalizedString(@"Canceled", @"File transfer canceled label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500312 break;
313 case lrc::api::interaction::Status::TRANSFER_ERROR:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400314 [result.statusLabel setTextColor:[NSColor redColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500315 [result.statusLabel setStringValue:NSLocalizedString(@"Failed", @"File transfer failed label")];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500316 break;
317 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400318 [result.statusLabel setTextColor:[NSColor textColor]];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500319 [result.statusLabel setStringValue:NSLocalizedString(@"Unjoinable", @"File transfer peer unjoinable label")];
320 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500321 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400322 result.transferedImage.image = nil;
323 [result.msgBackground setHidden:NO];
324 [result invalidateImageConstraints];
325 NSString* name = @(interaction.body.c_str());
326 if (name.length > 0) {
327 if (([name rangeOfString:@"/"].location != NSNotFound)) {
328 NSArray *listItems = [name componentsSeparatedByString:@"/"];
329 NSString* name1 = listItems.lastObject;
330 fileName = name1;
331 } else {
332 fileName = name;
333 }
334 }
335 result.transferedFileName.stringValue = fileName;
336 if (status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
337 NSImage* image = [self getImageForFilePath:name];
338 if (([name rangeOfString:@"/"].location == NSNotFound)) {
339 image = [self getImageForFilePath:[self getDataTransferPath:interactionID]];
340 }
341 if(image != nil) {
342 result.transferedImage.image = [image roundCorners:14];
343 [result updateImageConstraint:image.size.width andHeight:image.size.height];
344 }
345 }
346 [result setupForInteraction:interactionID];
347 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
348 NSString* timeString = [self timeForMessage: msgTime];
349 result.timeLabel.stringValue = timeString;
350 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
351 if (!isOutgoing) {
352 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
353 auto* conv = [self getCurrentConversation];
354 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
355 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500356 return result;
357}
358
Anthony Léonard2382b562017-12-13 15:51:28 -0500359#pragma mark - NSTableViewDelegate methods
360- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400361{
362 return YES;
363}
364
Anthony Léonard2382b562017-12-13 15:51:28 -0500365- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400366{
Anthony Léonard2382b562017-12-13 15:51:28 -0500367 return NO;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400368}
369
Anthony Léonard2382b562017-12-13 15:51:28 -0500370- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400371{
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400372
Anthony Léonard2382b562017-12-13 15:51:28 -0500373 auto* conv = [self getCurrentConversation];
374
375 if (conv == nil)
376 return nil;
377
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500378 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500379
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500380 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500381
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400382 IMTableCellView* result;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400383 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500384 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
385
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500386 switch (interaction.type) {
387 case lrc::api::interaction::Type::TEXT:
388 if (isOutgoing) {
389 result = [tableView makeViewWithIdentifier:@"RightMessageView" owner:self];
390 } else {
391 result = [tableView makeViewWithIdentifier:@"LeftMessageView" owner:self];
392 }
Anthony Léonarddd85dee2018-02-19 14:34:35 -0500393 if (interaction.status == lrc::api::interaction::Status::UNREAD)
394 convModel_->setInteractionRead(convUid_, it->first);
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500395 break;
396 case lrc::api::interaction::Type::INCOMING_DATA_TRANSFER:
397 case lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400398 return [self configureViewforTransfer:interaction interactionID: it->first tableView:tableView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500399 break;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500400 case lrc::api::interaction::Type::CONTACT:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400401 case lrc::api::interaction::Type::CALL: {
402 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
403 NSString* timeString = [self timeForMessage: msgTime];
404 return [self makeGenericInteractionViewForTableView:tableView withText:@(interaction.body.c_str()) andTime:timeString];
405 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500406 default: // If interaction is not of a known type
407 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400408 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400409 MessageSequencing sequence = [self computeSequencingFor:row];
410 BubbleType type = SINGLE;
411 if (sequence == FIRST_WITHOUT_TIME || sequence == FIRST_WITH_TIME) {
412 type = FIRST;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400413 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400414 if (sequence == MIDDLE_IN_SEQUENCE) {
415 type = MIDDLE;
416 }
417 if (sequence == LAST_IN_SEQUENCE) {
418 type = LAST;
419 }
420 result.msgBackground.type = type;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500421 [result setupForInteraction:it->first];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400422 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400423 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400424 [result.msgBackground setNeedsDisplay:YES];
425 [result setNeedsDisplay:YES];
426 [result.timeBox setNeedsDisplay:YES];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400427
428 NSMutableAttributedString* msgAttString =
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400429 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@",@(interaction.body.c_str())]
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400430 attributes:[self messageAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400431
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400432 CGSize messageSize = [self sizeFor: @(interaction.body.c_str()) maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400433
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400434 [result updateMessageConstraint:messageSize.width andHeight:messageSize.height timeIsVisible:shouldDisplayTime isTopPadding: shouldApplyPadding];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400435 [[result.msgView textStorage] appendAttributedString:msgAttString];
436 [result.msgView checkTextInDocument:nil];
Anthony Léonard2382b562017-12-13 15:51:28 -0500437
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400438 if (shouldDisplayTime) {
439 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
440 NSString* timeString = [self timeForMessage: msgTime];
441 result.timeLabel.stringValue = timeString;
Anthony Léonard64e19672018-01-18 16:40:34 -0500442 }
443
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400444 bool shouldDisplayAvatar = (sequence != MIDDLE_IN_SEQUENCE && sequence != FIRST_WITHOUT_TIME
445 && sequence != FIRST_WITH_TIME) ? YES : NO;
446 [result.photoView setHidden:!shouldDisplayAvatar];
447 if (!isOutgoing && shouldDisplayAvatar) {
448 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
449 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
450 }
451 [result.messageStatus setHidden:YES];
452 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
453 if (interaction.status == lrc::api::interaction::Status::SENDING) {
454 [result.messageStatus setHidden:NO];
455 [result.sendingMessageIndicator startAnimation:nil];
456 [result.messageFailed setHidden:YES];
457 } else if (interaction.status == lrc::api::interaction::Status::FAILED) {
458 [result.messageStatus setHidden:NO];
459 [result.sendingMessageIndicator setHidden:YES];
460 [result.messageFailed setHidden:NO];
461 }
462 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400463 return result;
464}
465
Anthony Léonard2382b562017-12-13 15:51:28 -0500466- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400467{
Anthony Léonard2382b562017-12-13 15:51:28 -0500468 double someWidth = tableView.frame.size.width * 0.7;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400469
Anthony Léonard2382b562017-12-13 15:51:28 -0500470 auto* conv = [self getCurrentConversation];
471
472 if (conv == nil)
473 return 0;
474
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500475 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500476
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500477 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500478
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400479 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500480
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400481 MessageSequencing sequence = [self computeSequencingFor:row];
482
483 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
484
485
486 if(interaction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER || interaction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER) {
487
488 if( interaction.status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
489 NSString* name = @(interaction.body.c_str());
490 NSImage* image = [self getImageForFilePath:name];
491 if (([name rangeOfString:@"/"].location == NSNotFound)) {
492 image = [self getImageForFilePath:[self getDataTransferPath:it->first]];
493 }
494 if (image != nil) {
495 return image.size.height + TIME_BOX_HEIGHT;
496 }
497 }
498 return BUBBLE_HEIGHT_FOR_TRANSFERED_FILE + TIME_BOX_HEIGHT;
499 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500500
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500501 if(interaction.type == lrc::api::interaction::Type::CONTACT || interaction.type == lrc::api::interaction::Type::CALL)
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400502 return GENERIC_CELL_HEIGHT;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500503
Anthony Léonard2382b562017-12-13 15:51:28 -0500504 // TODO Implement interactions other than messages
505 if(interaction.type != lrc::api::interaction::Type::TEXT) {
506 return 0;
507 }
508
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400509 CGSize messageSize = [self sizeFor: @(interaction.body.c_str()) maxWidth:tableView.frame.size.width * 0.7];
510 CGFloat singleLignMessageHeight = 15;
511
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400512 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
513
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400514 if (shouldDisplayTime) {
515 return MAX(messageSize.height + TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2,
516 TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2 + singleLignMessageHeight);
517 }
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400518 if(shouldApplyPadding) {
519 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2 + 15,
520 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2 + 15);
521 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400522 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2,
523 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2);
524}
525
526#pragma mark - message view parameters
527
528-(NSString *) getDataTransferPath:(uint64_t)interactionId {
529 lrc::api::datatransfer::Info info = {};
530 convModel_->getTransferInfo(interactionId, info);
531 double convertData = static_cast<double>(info.totalSize);
532 return @(info.path.c_str());
533}
534
535-(NSImage*) getImageForFilePath: (NSString *) path {
536 if (path.length <= 0) {return nil;}
537 if (![[NSFileManager defaultManager] fileExistsAtPath: path]) {return nil;}
538 NSImage* transferedImage = [[NSImage alloc] initWithContentsOfFile: path];
539 if(transferedImage != nil) {
540 return [transferedImage imageResizeInsideMax: MAX_TRANSFERED_IMAGE_SIZE];
541 }
542 return nil;
543}
544
545-(CGSize) sizeFor:(NSString *) message maxWidth:(CGFloat) width {
546 CGFloat horizaontalMargin = 6;
Anthony Léonard2382b562017-12-13 15:51:28 -0500547 NSMutableAttributedString* msgAttString =
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400548 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@", message]
Anthony Léonard2382b562017-12-13 15:51:28 -0500549 attributes:[self messageAttributes]];
550
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400551 CGFloat finalWidth = MIN(msgAttString.size.width + horizaontalMargin * 2, width);
552 NSRect frame = NSMakeRect(0, 0, finalWidth, msgAttString.size.height);
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400553 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400554 [[tv textStorage] setAttributedString:msgAttString];
555 [tv sizeToFit];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400556 return tv.frame.size;
557}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400558
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400559-(MessageSequencing) computeSequencingFor:(NSInteger) row {
560 auto* conv = [self getCurrentConversation];
561 if (conv == nil)
562 return SINGLE_WITHOUT_TIME;
563 auto it = conv->interactions.begin();
564 std::advance(it, row);
565 auto interaction = it->second;
566 if (interaction.type != lrc::api::interaction::Type::TEXT) {
567 return SINGLE_WITH_TIME;
568 }
569 if (row == 0) {
570 if (it == conv->interactions.end()) {
571 return SINGLE_WITH_TIME;
572 }
573 auto nextIt = it;
574 nextIt++;
575 auto nextInteraction = nextIt->second;
576 if ([self sequenceChangedFrom:interaction to: nextInteraction]) {
577 return SINGLE_WITH_TIME;
578 }
579 return FIRST_WITH_TIME;
580 }
581
582 if (row == conversationView.numberOfRows - 1) {
583 if(it == conv->interactions.begin()) {
584 return SINGLE_WITH_TIME;
585 }
586 auto previousIt = it;
587 previousIt--;
588 auto previousInteraction = previousIt->second;
589 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
590 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
591 if (!timeChanged && !authorChanged) {
592 return LAST_IN_SEQUENCE;
593 }
594 if (!timeChanged && authorChanged) {
595 return SINGLE_WITHOUT_TIME;
596 }
597 return SINGLE_WITH_TIME;
598 }
599 if(it == conv->interactions.begin() || it == conv->interactions.end()) {
600 return SINGLE_WITH_TIME;
601 }
602 auto previousIt = it;
603 previousIt--;
604 auto previousInteraction = previousIt->second;
605 auto nextIt = it;
606 nextIt++;
607 auto nextInteraction = nextIt->second;
608
609 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
610 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
611 bool sequenceWillChange = [self sequenceChangedFrom:interaction to: nextInteraction];
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400612 if (previousInteraction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER ||
613 previousInteraction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER) {
614 if(!sequenceWillChange) {
615 return FIRST_WITH_TIME;
616 }
617 return SINGLE_WITH_TIME;
618 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400619 if (!sequenceWillChange) {
620 if (!timeChanged && !authorChanged) {
621 return MIDDLE_IN_SEQUENCE;
622 }
623 if (timeChanged) {
624 return FIRST_WITH_TIME;
625 }
626 return FIRST_WITHOUT_TIME;
627 } if (!timeChanged && !authorChanged) {
628 return LAST_IN_SEQUENCE;
629 } if (timeChanged) {
630 return SINGLE_WITH_TIME;
631 }
632 return SINGLE_WITHOUT_TIME;
633}
634
635-(bool) sequenceChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
636 return ([self sequenceTimeChangedFrom:firstInteraction to:secondInteraction] || [self sequenceAuthorChangedFrom:firstInteraction to:secondInteraction]);
637}
638
639-(bool) sequenceTimeChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
640 bool timeChanged = NO;
641 NSDate* firstMessageTime = [NSDate dateWithTimeIntervalSince1970:firstInteraction.timestamp];
642 NSDate* secondMessageTime = [NSDate dateWithTimeIntervalSince1970:secondInteraction.timestamp];
643 bool hourComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitHour];
644 bool minutComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitMinute];
645 if(hourComp != NSOrderedSame || minutComp != NSOrderedSame) {
646 timeChanged = YES;
647 }
648 return timeChanged;
649}
650
651-(bool) sequenceAuthorChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
652 bool authorChanged = YES;
653 bool isOutgoing = lrc::api::interaction::isOutgoing(firstInteraction);
654 if ((secondInteraction.type == lrc::api::interaction::Type::TEXT) && (isOutgoing == lrc::api::interaction::isOutgoing(secondInteraction))) {
655 authorChanged = NO;
656 }
657 return authorChanged;
658}
659
660-(NSString *)timeForMessage:(NSDate*) msgTime {
661 NSDate *today = [NSDate date];
662 NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
663 if ([[NSCalendar currentCalendar] compareDate:today
664 toDate:msgTime
665 toUnitGranularity:NSCalendarUnitYear]!= NSOrderedSame) {
666 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle];
667 }
668
669 if ([[NSCalendar currentCalendar] compareDate:today
670 toDate:msgTime
671 toUnitGranularity:NSCalendarUnitDay]!= NSOrderedSame ||
672 [[NSCalendar currentCalendar] compareDate:today
673 toDate:msgTime
674 toUnitGranularity:NSCalendarUnitMonth]!= NSOrderedSame) {
675 [dateFormatter setDateFormat:@"MMM dd, HH:mm"];
676 return [dateFormatter stringFromDate:msgTime];
677 }
678
679 [dateFormatter setDateFormat:@"HH:mm"];
680 return [dateFormatter stringFromDate:msgTime];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400681}
682
Anthony Léonard2382b562017-12-13 15:51:28 -0500683#pragma mark - NSTableViewDataSource
684
685- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
686{
687 auto* conv = [self getCurrentConversation];
688
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500689 if (conv)
690 return conv->interactions.size();
691 else
692 return 0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500693}
694
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400695#pragma mark - Text formatting
696
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400697- (NSMutableDictionary*) messageAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400698{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400699 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400700 attrs[NSForegroundColorAttributeName] = [NSColor labelColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400701 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400702 return attrs;
703}
704
705- (NSParagraphStyle*) paragraphStyle
706{
707 /*
708 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
709 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
710 to copy, we use the default one.
711
712 The default values supplied by the default NSParagraphStyle are:
713 Alignment NSNaturalTextAlignment
714 Tab stops 12 left-aligned tabs, spaced by 28.0 points
715 Line break mode NSLineBreakByWordWrapping
716 All others 0.0
717 */
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400718 NSMutableParagraphStyle* aMutableParagraphStyle =
719 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
720 [aMutableParagraphStyle setHeadIndent:1.0];
721 [aMutableParagraphStyle setFirstLineHeadIndent:1.0];
722 return aMutableParagraphStyle;
723}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400724
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400725- (void)acceptFile:(uint64_t)interactionID {
726 NSURL *downloadsURL = [[NSFileManager defaultManager]
727 URLForDirectory:NSDownloadsDirectory
728 inDomain:NSUserDomainMask appropriateForURL:nil
729 create:YES error:nil];
730 auto& inter = [self getCurrentConversation]->interactions.find(interactionID)->second;
731 if (convModel_ && !convUid_.empty()) {
732 NSURL *bUrl = [downloadsURL URLByAppendingPathComponent:@(inter.body.c_str())];
733 const char* fullPath = [bUrl fileSystemRepresentation];
734 convModel_->acceptTransfer(convUid_, interactionID, fullPath);
735 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400736}
737
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500738#pragma mark - Actions
739
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400740- (void)acceptIncomingFile:(id)sender {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500741 auto interId = [(IMTableCellView*)[[sender superview] superview] interaction];
742 auto& inter = [self getCurrentConversation]->interactions.find(interId)->second;
743 if (convModel_ && !convUid_.empty()) {
744 NSSavePanel* filePicker = [NSSavePanel savePanel];
745 [filePicker setNameFieldStringValue:@(inter.body.c_str())];
746
747 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
748 const char* fullPath = [[filePicker URL] fileSystemRepresentation];
749 convModel_->acceptTransfer(convUid_, interId, fullPath);
750 }
751 }
752}
753
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400754- (void)declineIncomingFile:(id)sender {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500755 auto inter = [(IMTableCellView*)[[sender superview] superview] interaction];
756 if (convModel_ && !convUid_.empty()) {
757 convModel_->cancelTransfer(convUid_, inter);
758 }
759}
760
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400761@end