Commit c48e73e5 authored by Kateryna Kostiuk's avatar Kateryna Kostiuk Committed by Kateryna Kostiuk
Browse files

conversation: support files drag and drop

Change-Id: If97d5b8602ab162b605b1df02c9528d6609d4f0c
parent 723d95da
......@@ -61,12 +61,8 @@ set(SHADERS_FILE "Shader.metallib")
add_custom_command (OUTPUT ${CMAKE_SOURCE_DIR}/Shader.metallib
COMMAND ${CMAKE_SOURCE_DIR}/generateShaderLib.sh
COMMENT "Creating Shader.metallib")
add_custom_target(
shader ALL
DEPENDS ${CMAKE_SOURCE_DIR}/Shader.metallib
)
add_custom_target(shader ALL
DEPENDS ${CMAKE_SOURCE_DIR}/Shader.metallib)
IF(NOT (${ENABLE_SPARKLE} MATCHES false))
MESSAGE("Sparkle auto-update enabled")
......@@ -244,6 +240,10 @@ SET(ringclient_VIEWS
src/views/CustomBackgroundView.mm
src/views/TextViewWithPlaceholder.h
src/views/TextViewWithPlaceholder.mm
src/views/FileToSendCollectionItem.h
src/views/FileToSendCollectionItem.mm
src/views/DraggingDestinationView.h
src/views/DraggingDestinationView.mm
)
SET(ringclient_OTHERS
......@@ -302,6 +302,7 @@ SET(ringclient_XIBS
CallInConferenceVC
ConnectToAccManagerVC
AccountBackupVC
FileToSendCollectionItem
)
# Icons
......
ic_file.png

1.52 KB

......@@ -43,4 +43,6 @@ namespace lrc {
-(NSViewController*) getMessagesView;
-(void)callFinished;
@end
......@@ -93,6 +93,10 @@ NSInteger const SEND_PANEL_MAX_HEIGHT = 120;
return messagesViewVC;
}
-(void)callFinished {
[messagesViewVC callFinished];
}
-(void) clearData {
cachedConv_ = nil;
convUid_ = "";
......
......@@ -22,9 +22,16 @@
#import <api/conversation.h>
#import <api/avmodel.h>
#import "RecordFileVC.h"
#import "views/DraggingDestinationView.h"
@interface PendingFile: NSObject
@property (retain) NSString* name;
@property (retain) NSString* size;
@property (retain) NSImage* preview;
@property (retain) NSURL* fileUrl;
@end
@interface MessagesVC : NSViewController <RecordingViewDelegate>
@interface MessagesVC : NSViewController <RecordingViewDelegate, DraggingDestinationDelegate>
-(void)setConversationUid:(const QString&)convUid model:(lrc::api::ConversationModel*)model;
-(void)clearData;
......@@ -36,7 +43,21 @@
*/
@property (retain) NSString* message;
/**
* This is a KVO method to bind the pending files collection view visibility
*/
@property BOOL hideFilesCollection;
/**
* This is a KVO method to bind the enable state of send button
*/
@property BOOL enableSendButton;
-(void) setAVModel: (lrc::api::AVModel*) avmodel;
-(void) checkIfcomposingMsg;
+ (NSMutableDictionary *) pendingFiles;
-(void)callFinished;
@end
......@@ -30,6 +30,7 @@
#import "views/IMTableCellView.h"
#import "views/MessageBubbleView.h"
#import "views/NSImage+Extensions.h"
#import "views/FileToSendCollectionItem.h"
#import "delegates/ImageManipulationDelegate.h"
#import "utils.h"
#import "views/NSColor+RingTheme.h"
......@@ -41,10 +42,14 @@
#import "RecordFileVC.h"
@implementation PendingFile
@end
@interface MessagesVC () <NSTableViewDelegate, NSTableViewDataSource, QLPreviewPanelDataSource, NSTextViewDelegate> {
@interface MessagesVC () <NSTableViewDelegate, NSTableViewDataSource, QLPreviewPanelDataSource, NSTextViewDelegate, NSCollectionViewDataSource> {
__unsafe_unretained IBOutlet NSTableView* conversationView;
__unsafe_unretained IBOutlet DraggingDestinationView* draggingDestinationView;
__unsafe_unretained IBOutlet NSCollectionView* pendingFilesCollectionView;
__unsafe_unretained IBOutlet NSView* containerView;
__unsafe_unretained IBOutlet TextViewWithPlaceholder* messageView;
__unsafe_unretained IBOutlet IconButton *sendFileButton;
......@@ -52,7 +57,6 @@
__unsafe_unretained IBOutlet IconButton *recordAudioButton;
__unsafe_unretained IBOutlet NSLayoutConstraint* sendPanelHeight;
__unsafe_unretained IBOutlet NSLayoutConstraint* messageHeight;
__unsafe_unretained IBOutlet NSLayoutConstraint* messagesBottomMargin;
__unsafe_unretained IBOutlet NSLayoutConstraint* textBottomConstraint;
IBOutlet NSPopover *recordMessagePopover;
......@@ -71,7 +75,7 @@
QMetaObject::Connection lastDisplayedChanged_;
NSString* previewImage;
NSMutableDictionary *pendingMessagesToSend;
RecordFileVC * recordingController;
RecordFileVC* recordingController;
}
@end
......@@ -126,6 +130,25 @@ typedef NS_ENUM(NSInteger, MessageSequencing) {
[[conversationView.enclosingScrollView contentView] setCopiesOnScroll:NO];
[messageView setFont: [NSFont systemFontOfSize: 14 weight: NSFontWeightLight]];
[conversationView setWantsLayer:YES];
draggingDestinationView.draggingDestinationDelegate = self;
}
-(void)callFinished {
[self reloadPendingFiles];
dispatch_async(dispatch_get_main_queue(), ^{
[conversationView scrollToEndOfDocument:nil];
[messageView.window makeFirstResponder: messageView];
});
}
+(NSMutableDictionary*)pendingFiles
{
static NSMutableDictionary* files = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
files = [[NSMutableDictionary alloc] init];
});
return files;
}
- (instancetype)initWithCoder:(NSCoder *)coder
......@@ -341,25 +364,10 @@ typedef NS_ENUM(NSInteger, MessageSequencing) {
dispatch_async(dispatch_get_main_queue(), ^{
[messageView.window makeFirstResponder: messageView];
});
conversationView.alphaValue = 0.0;
[conversationView reloadData];
[conversationView scrollToEndOfDocument:nil];
CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeIn.fromValue = [NSNumber numberWithFloat:0.0];
fadeIn.toValue = [NSNumber numberWithFloat:1.0];
fadeIn.duration = 0.4f;
[conversationView.layer addAnimation:fadeIn forKey:fadeIn.keyPath];
conversationView.alphaValue = 1;
auto* conv = [self getCurrentConversation];
if (conv == nil)
return;
try {
[sendFileButton setEnabled:(convModel_->owner.contactModel->getContact(conv->participants[0]).profileInfo.type != lrc::api::profile::Type::SIP)];
} catch (std::out_of_range& e) {
NSLog(@"contact out of range");
}
NSString* name = bestNameForConversation(*conv, *convModel_);
NSString *placeholder = [NSString stringWithFormat:@"%@%@", @"Write to ", name];
......@@ -371,6 +379,22 @@ typedef NS_ENUM(NSInteger, MessageSequencing) {
nil];
NSAttributedString* attributedPlaceholder = [[NSAttributedString alloc] initWithString: placeholder attributes:nameAttrs];
messageView.placeholderAttributedString = attributedPlaceholder;
[self reloadPendingFiles];
conversationView.alphaValue = 0.0;
[conversationView reloadData];
[conversationView scrollToEndOfDocument:nil];
CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeIn.fromValue = [NSNumber numberWithFloat:0.0];
fadeIn.toValue = [NSNumber numberWithFloat:1.0];
fadeIn.duration = 0.4f;
[conversationView.layer addAnimation:fadeIn forKey:fadeIn.keyPath];
conversationView.alphaValue = 1;
try {
[sendFileButton setEnabled:(convModel_->owner.contactModel->getContact(conv->participants[0]).profileInfo.type != lrc::api::profile::Type::SIP)];
} catch (std::out_of_range& e) {
NSLog(@"contact out of range");
}
}
#pragma mark - configure cells
......@@ -960,7 +984,6 @@ typedef NS_ENUM(NSInteger, MessageSequencing) {
if (MESSAGE_VIEW_DEFAULT_HEIGHT != lineHeight) {
MESSAGE_VIEW_DEFAULT_HEIGHT = lineHeight;
}
messagesBottomMargin.constant = newSendPanelHeight;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self scrollToBottom];
messageHeight.constant = msgHeight;
......@@ -1068,6 +1091,14 @@ typedef NS_ENUM(NSInteger, MessageSequencing) {
unichar separatorChar = NSLineSeparatorCharacter;
NSString *separatorString = [NSString stringWithCharacters:&separatorChar length:1];
text = [text stringByReplacingOccurrencesOfString: separatorString withString: @"\n"];
// send files
NSMutableArray* files = [MessagesVC pendingFiles][convUid_.toNSString()];
for (PendingFile* file : files) {
convModel_->sendFile(convUid_, QString::fromNSString(file.fileUrl.path), QString::fromNSString(file.name));
}
[MessagesVC.pendingFiles removeObjectForKey: convUid_.toNSString()];
[self reloadPendingFiles];
if (text && text.length > 0) {
auto* conv = [self getCurrentConversation];
if (conv == nil)
......@@ -1086,7 +1117,6 @@ typedef NS_ENUM(NSInteger, MessageSequencing) {
if(messageHeight.constant != MESSAGE_VIEW_DEFAULT_HEIGHT) {
sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
messageHeight.constant = MESSAGE_VIEW_DEFAULT_HEIGHT;
messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
textBottomConstraint.constant = BOTTOM_MARGIN;
[self scrollToBottom];
}
......@@ -1143,18 +1173,10 @@ typedef NS_ENUM(NSInteger, MessageSequencing) {
NSOpenPanel* filePicker = [NSOpenPanel openPanel];
[filePicker setCanChooseFiles:YES];
[filePicker setCanChooseDirectories:NO];
[filePicker setAllowsMultipleSelection:NO];
[filePicker setAllowsMultipleSelection:YES];
if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
if ([[filePicker URLs] count] == 1) {
NSURL* url = [[filePicker URLs] objectAtIndex:0];
const char* fullPath = [url fileSystemRepresentation];
NSString* fileName = [url lastPathComponent];
if (convModel_) {
auto* conv = [self getCurrentConversation];
convModel_->sendFile(convUid_, QString::fromStdString(fullPath), QString::fromNSString(fileName));
}
}
[self filesDragged: [filePicker URLs]];
}
}
......@@ -1163,7 +1185,7 @@ typedef NS_ENUM(NSInteger, MessageSequencing) {
- (BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector {
if (commandSelector == @selector(insertNewline:)) {
if(self.message.length > 0) {
if(self.message.length > 0 || [(NSMutableArray*)MessagesVC. pendingFiles[convUid_.toNSString()] count] > 0) {
[self sendMessage: nil];
return YES;
}
......@@ -1173,8 +1195,9 @@ typedef NS_ENUM(NSInteger, MessageSequencing) {
return NO;
}
-(void) textDidChange:(NSNotification *)notification {
-(void)textDidChange:(NSNotification *)notification {
[self checkIfComposingMsg];
self.enableSendButton = self.message.length > 0 || [(NSMutableArray*)MessagesVC. pendingFiles[convUid_.toNSString()] count] > 0;
}
- (void) checkIfComposingMsg {
......@@ -1226,7 +1249,6 @@ typedef NS_ENUM(NSInteger, MessageSequencing) {
}
}
- (void)removeFromResponderChain {
if (conversationView.window &&
[[conversationView.window nextResponder] isEqual:self]) {
......@@ -1235,4 +1257,78 @@ typedef NS_ENUM(NSInteger, MessageSequencing) {
}
}
#pragma mark - NSCollectionViewDataSource
- (NSInteger)collectionView:(NSCollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [(NSMutableArray*)MessagesVC.pendingFiles[convUid_.toNSString()] count];
}
- (NSCollectionViewItem*)collectionView:(NSCollectionView *)collectionView itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {
FileToSendCollectionItem* fileCell = [collectionView makeItemWithIdentifier:@"FileToSendCollectionItem" forIndexPath:indexPath];
PendingFile* file = MessagesVC.pendingFiles[convUid_.toNSString()][indexPath.item];
fileCell.filePreview.image = file.preview;
fileCell.fileName.stringValue = file.name;
fileCell.fileName.toolTip = file.name;
fileCell.fileSize.stringValue = file.size;
[fileCell.closeButton setAction:@selector(removePendingFile:)];
fileCell.closeButton.tag = indexPath.item;
[fileCell.closeButton setTarget:self];
return fileCell;
}
#pragma mark - DraggingDestinationDelegate
-(void)filesDragged:(NSArray*)urls {
[self prepareFilesToSend: urls];
}
-(NSString*)convertBytedToString:(double)bytes {
if (bytes <= 1000) {
return [NSString stringWithFormat:@"%.2f%@", bytes, @" B"];
} else if (bytes <= 1e6) {
return [NSString stringWithFormat:@"%.2f%@",(bytes * 1e-3), @" KB"];
} else if (bytes <= 1e9) {
return [NSString stringWithFormat:@"%.2f%@",(bytes * 1e-6), @" MB"];
}
return [NSString stringWithFormat:@"%.2f%@",(bytes * 1e-9), @" GB"];
}
-(void)prepareFilesToSend:(NSArray*)urls {
NSMutableArray* files = [[NSMutableArray alloc] init];
NSMutableArray* existingFiles = MessagesVC.pendingFiles[convUid_.toNSString()];
[files addObjectsFromArray: existingFiles];
for (NSURL* url : urls) {
NSString* filePath = [url path];
NSImage* preview = [[NSImage alloc] initWithContentsOfFile: filePath];
NSString* name = [url lastPathComponent];
NSData* documentBytes = [[NSData alloc] initWithContentsOfFile: filePath];
PendingFile* file = [[PendingFile alloc] init];
file.name = name;
file.size = [self convertBytedToString: documentBytes.length];
file.preview = preview;
file.fileUrl = url;
[files addObject: file];
}
MessagesVC.pendingFiles[convUid_.toNSString()] = files;
[self reloadPendingFiles];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self scrollToBottom];
});
}
- (void)removePendingFile:(id)sender {
NSButton* closeButton = (NSButton*)sender;
NSInteger index = closeButton.tag;
if(index < 0) {
return;
}
[MessagesVC.pendingFiles[convUid_.toNSString()] removeObjectAtIndex:index];
[self reloadPendingFiles];
}
- (void)reloadPendingFiles {
self.hideFilesCollection = [(NSMutableArray*)MessagesVC .pendingFiles[convUid_.toNSString()] count]== 0;
self.enableSendButton = self.message.length > 0 || [(NSMutableArray*)MessagesVC. pendingFiles[convUid_.toNSString()] count] > 0;
[pendingFilesCollectionView reloadData];
}
@end
......@@ -649,6 +649,7 @@ typedef NS_ENUM(NSInteger, ViewState) {
-(void) callFinished {
[self changeViewTo:SHOW_CONVERSATION_SCREEN];
[conversationVC callFinished];
}
-(void) showConversation:(NSString* )conversationId forAccount:(NSString*)accountId {
......
/*
* Copyright (C) 2021 Savoir-faire Linux Inc.
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@protocol DraggingDestinationDelegate
-(void) filesDragged:(NSArray*)urls;
@end
@interface DraggingDestinationView : NSView <NSDraggingDestination>
{
/**
* Indicate when dragged file enter drop zone
*/
BOOL highlight;
}
/**
* Delegate to inform about files to process
*/
@property (nonatomic) id <DraggingDestinationDelegate> draggingDestinationDelegate;
@end
NS_ASSUME_NONNULL_END
/*
* Copyright (C) 2021 Savoir-faire Linux Inc.
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import "DraggingDestinationView.h"
@implementation DraggingDestinationView
- (id)initWithFrame:(NSRect)frame
{
if (self = [super initWithFrame: frame])
[self registerForDraggedTypes:[NSArray arrayWithObjects: NSPasteboardTypeURL, nil]];
return self;
}
-(void)drawRect:(NSRect)rect
{
[super drawRect:rect];
if (highlight) {
[[[NSColor blackColor] colorWithAlphaComponent:0.8] set];
[NSBezierPath fillRect: rect];
}
NSRect rectText = rect;
NSDictionary* attributes = nil;
NSString* title = highlight ?
NSLocalizedString(@"Drop files to send", @"drop files") : @"";
NSMutableParagraphStyle* style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[style setLineBreakMode:NSLineBreakByWordWrapping];
[style setAlignment:NSCenterTextAlignment];
NSFont* font= [NSFont systemFontOfSize: 32 weight: NSFontWeightSemibold];
attributes = [[NSDictionary alloc] initWithObjectsAndKeys:
style, NSParagraphStyleAttributeName,
font,NSFontAttributeName,
[NSColor whiteColor],
NSForegroundColorAttributeName, nil];
rectText.size = [title sizeWithAttributes: attributes];
rectText.origin.x = floor( NSMidX(rect) - rectText.size.width / 2);
rectText.origin.y = floor( NSMidY([self bounds]) - rectText.size.height / 2 );
[title drawInRect:rectText withAttributes: attributes];
}
#pragma mark - Destination Operations
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
highlight = true;
[self setNeedsDisplay: true];
return NSDragOperationCopy;
}
- (void)draggingExited:(id <NSDraggingInfo>)sender
{
highlight = false;
[self setNeedsDisplay: true];
}
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
{
highlight = false;
[self setNeedsDisplay: true];
return true;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
NSArray* classArray = [NSArray arrayWithObject:[NSURL class]];
NSArray* arrayOfURLs = [[sender draggingPasteboard] readObjectsForClasses:classArray options:nil];
[self.draggingDestinationDelegate filesDragged: arrayOfURLs];
return true;
}
@end
/*
* Copyright (C) 2021 Savoir-faire Linux Inc.
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface FileToSendCollectionItem : NSCollectionViewItem
@property (nonatomic, strong) IBOutlet NSImageView* filePreview;
@property (nonatomic, strong) IBOutlet NSImageView* placeholderPreview;
@property (nonatomic, strong) IBOutlet NSTextField* fileName;
@property (nonatomic, strong) IBOutlet NSTextField* fileSize;
@property (nonatomic, strong) IBOutlet NSButton* closeButton;
@end
NS_ASSUME_NONNULL_END
/*
* Copyright (C) 2021 Savoir-faire Linux Inc.
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import "FileToSendCollectionItem.h"
#import "NSColor+RingTheme.h"
@interface FileToSendCollectionItem ()
@end
@implementation FileToSendCollectionItem
- (void)viewDidLoad {
[super viewDidLoad];
self.placeholderPreview.image = [NSColor image: [NSImage imageNamed:@"ic_file.png"] tintedWithColor: [NSColor windowFrameTextColor]];
self.closeButton.image = [NSColor image: [NSImage imageNamed:@"ic_action_cancel.png"] tintedWithColor: [NSColor windowFrameTextColor]];
[NSDistributedNotificationCenter.defaultCenter addObserver:self selector:@selector(themeChanged:) name:@"AppleInterfaceThemeChangedNotification" object: nil];
}
-(void) deinit {
[NSDistributedNotificationCenter.defaultCenter removeObserver:self];
}
-(void)themeChanged:(NSNotification *) notification {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
self.placeholderPreview.image = [NSColor image: [NSImage imageNamed:@"ic_file.png"] tintedWithColor: [NSColor windowFrameTextColor]];
self.closeButton.image = [NSColor image: [NSImage imageNamed:@"ic_action_cancel.png"] tintedWithColor: [NSColor windowFrameTextColor]];
});
}
@end
This diff is collapsed.
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="FileToSendCollectionItem">
<connections>
<outlet property="closeButton" destination="VNn-a7-nCL" id="Fir-qs-MIO"/>
<outlet property="fileName" destination="ijl-x9-Fsc" id="ayy-3D-Pku"/>
<outlet property="filePreview" destination="MNG-EM-Qlk" id="G1Y-8c-e1F"/>
<outlet property="fileSize" destination="3gS-G1-CGL" id="phJ-aQ-Jc4"/>
<outlet property="placeholderPreview" destination="2xL-sU-hKn" id="kzn-2u-kcL"/>
<outlet property="view" destination=