From f631742bf9ab3fc5ebc11715c0f26f8a6737f099 Mon Sep 17 00:00:00 2001 From: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> Date: Thu, 27 Sep 2018 17:08:20 -0400 Subject: [PATCH] call: implement leave message view Change-Id: Ie4ab6134d39907d108e6a5bf39a863cb56ede684 Reviewed-by: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> --- CMakeLists.txt | 7 + data/dark/ic_audio_file.png | Bin 0 -> 756 bytes data/dark/ic_exit.png | Bin 0 -> 399 bytes data/dark/ic_stoprecord.png | Bin 0 -> 1162 bytes src/AppDelegate.mm | 2 +- src/ConversationVC.h | 18 ++- src/ConversationVC.mm | 34 +++- src/LeaveMessageVC.h | 45 ++++++ src/LeaveMessageVC.mm | 203 ++++++++++++++++++++++++ src/LrcModelsProtocol.h | 3 + src/MessagesVC.mm | 76 +++++---- src/RingWindowController.mm | 34 ++-- ui/Base.lproj/LeaveMessageVC.xib | 257 +++++++++++++++++++++++++++++++ 13 files changed, 630 insertions(+), 49 deletions(-) create mode 100644 data/dark/ic_audio_file.png create mode 100644 data/dark/ic_exit.png create mode 100644 data/dark/ic_stoprecord.png create mode 100644 src/LeaveMessageVC.h create mode 100644 src/LeaveMessageVC.mm create mode 100644 ui/Base.lproj/LeaveMessageVC.xib diff --git a/CMakeLists.txt b/CMakeLists.txt index 9360a1bb..8cfc8850 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,6 +167,8 @@ SET(ringclient_CONTROLLERS src/AddSIPAccountVC.h src/AccountSettingsVC.mm src/AccountSettingsVC.h + src/LeaveMessageVC.mm + src/LeaveMessageVC.h ) SET(ringclient_BACKENDS @@ -261,6 +263,7 @@ SET(ringclient_XIBS MessageCells AddSIPAccountVC AccountSettings + LeaveMessageVC ) # Icons @@ -269,6 +272,10 @@ SET(myApp_ICON ${CMAKE_CURRENT_SOURCE_DIR}/data/appicon.icns) SET_SOURCE_FILES_PROPERTIES(${myApp_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) SET(ring_ICONS +${CMAKE_CURRENT_SOURCE_DIR}/data/dark/ic_audio_file.png +${CMAKE_CURRENT_SOURCE_DIR}/data/dark/ic_exit.png +${CMAKE_CURRENT_SOURCE_DIR}/data/dark/ic_stoprecord.png +${CMAKE_CURRENT_SOURCE_DIR}/data/dark/ic_action_audio.png ${CMAKE_CURRENT_SOURCE_DIR}/data/dark/ic_action_audio.png ${CMAKE_CURRENT_SOURCE_DIR}/data/dark/ic_folder.png ${CMAKE_CURRENT_SOURCE_DIR}/data/dark/ic_action_block.png diff --git a/data/dark/ic_audio_file.png b/data/dark/ic_audio_file.png new file mode 100644 index 0000000000000000000000000000000000000000..6130da1025bad9198cfcf4a3109349a611d8bbbd GIT binary patch literal 756 zcmeAS@N?(olHy`uVBq!ia0y~yVDJWE4mJh`1`EHcR}2gcY)RhkE)4%caKYZ?lNlJ8 zR(iTPhE&XXd)vN;*HPxc$MdqCbDaM4yD6zeaS1ssUbHy;ZmZX#3llUZXk5^8;jk3y zbqW!>su{q^+3Doy)Y#PLQP5R8ea`7t^Y{1um8IwJJE@ZY{cqW`?f1{TIk)Go`@(<| z9h+z5Iqqz{Smj?@CzV;!*z<T7ThaMIF<wsTEgOp^IQ5D-#iwPTpQRWQv2Ih|nO`=2 z*-YnD!z0}bQm=h$X^@+?dQGOx=j|JAwWqAxl;IF@ZWD*twB0&Pt)-4)j)!(Bbu%Q! zPI5haXqQ^IRBLIVm?`J$RXSM$*HR;SA|tM+8oDHUx6cSsQD?V!W^0#Rn_nHH`bnei zd$8lWO?o^l=W*^i@U*T;{IJ0v`Rrf3XI7@i$|S{BZ&d%fU{1<hsifF+UWL^>hgrJT zfY7aqvr+BL5{JWU4tigeSs8ds<F#Gc`J3jwnsbg_e$kSa@z~&w&G~(TZw@D4H9p+C zQ1WZW;x+Y#i=vk8Ib7_M6dm$7SElW+LD$>8n_Nv#@?@Hnhq;?xVU0}O5_!$v=5X|% zEDP~1(bvM?1l*Mke_$}rWM;tZ)U(f|->nea|NA4u{p$O^uBsCs-~EudUguy`O;?u1 z^m(T8`&qh_@*^LLL~@Ema&8F>%g(pToxIiS!6((JW+$Z%R(}5PxIon8;?wXXakCXw zm!xtI`Q_v)%-`2JWoyXSH}55;v2L?AQA(9P-r4eF-#z;dl~1!uH4Ohb>qhD{ctj?5 zeq5h*GKtaURMmWm`pwgH#2l8axy`w8az#kEQnQfe>v^wKx5)qR__?+!{8fgBTc^wU z&3X^T_*T`i#&$nlwfyY*Lh<~j;#afP)RxK@ZVlhG$oKTzYr!e10l1ODi(4Oj*6V0_ jo%dRBLQ90p@*{g)Sa${wcYz!O0|SGntDnm{r-UW|+rCbF literal 0 HcmV?d00001 diff --git a/data/dark/ic_exit.png b/data/dark/ic_exit.png new file mode 100644 index 0000000000000000000000000000000000000000..4139f44e2b97c0d21fd7ccd02d502a3be43c73fe GIT binary patch literal 399 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hE<t`_ZS!$*pj^6T^Rm@;DWu&Co?cG zx_G)chE&XXJImYeuz`pxe+Or@<DamDe3H}dDlQS5VZ_7y#H%-Ip~|-RoA239O82~X zcH-kBeFr6yed5-M$yl3uZ|qaOsb3WPSV=jV@fAZ>p4pt}r;;%alMOX$`8P0>GMI`j z;9GEv=?Y`mr?nbZXJ@VYCs!eKg<tUj^A6!Fc5DG$77X8;cDY|+n#XGDAHY51v&VBs zn@J7g57;XfFK*FbI(2rs%v}{1fdcM5^G|MC+}y&!^dR5hkIm%emHr({3@$=SoD7XE z8B7Z{w`^s4_WWy>wbuIRKD7j<xS#HaR$NZxT>Q1cpx?o}O@bwQ;xE$~znnF`F5jT; ztTLZZJV?Jo<dSZuEl1Y81!5EGB5Ra>_D>8l|GIwTrF~hkC8ymyzp82GPg&E*x`KJd zMv!M-vgX{>mPvHlGV_b=0j4XAJ%-05WcLfmsL$S%UG|iLfq}u()z4*}Q$iB};s2c7 literal 0 HcmV?d00001 diff --git a/data/dark/ic_stoprecord.png b/data/dark/ic_stoprecord.png new file mode 100644 index 0000000000000000000000000000000000000000..085d7e14ffd4ee8f590aa617c97a95a49f09a405 GIT binary patch literal 1162 zcmeAS@N?(olHy`uVBq!ia0y~yV2A=?4mJh`28$JrObiSRY)RhkE)4%caKYZ?lNlIT z>^)r^Ln`LHo$H$)>MC)(e%h<t%Zp6!I%Fp;j3_%=;@!DKWZR=fvsT>Y)X91joh2Mz z$8u>&JcAtrr;duip(fUrD;cTEI#*i1_U>ae{c>#Ijv3A-*W*6VF+P80j&XV2N&9l+ z?|1(HDSl_0{>;W$T&UAUX`+XUP^ZhHB{t%>4(PpMbZba_U|_%^!?LSk`sVNk`^Kwp zzOp~C`@j&vu#a)uo?YuW)-%X6hc~z%=v#WN;q(D_hyBOvrcCln5Zcja`8(%u?lRv8 zQXlLtJY1Ttx=^}!neHRKJ88d|e+X5aELC^@WMjaq^Vt4@n=bSG)Om{@Nfh*dU^IVK zop2{{|K5v?XBcm{@Nb>Dtub=JGY#hFJPhR=@=HSEncHJn9!@GO$-7*W%_~tnp<VbM z(>x)&EsIt$zhAL@(V56Q_k@365-V`Cls5HCW@BIIe2M3G?$s6%31%}6`Mzzh4rm^o zv!<dqEAGdVC}y^+m$W>1i=#!Xc&^V<|4{qwj^47Qt80SNc3Qgabg#K}i|t;3ryl#; z8JyoP%>BN$x?*0(tg;K8t6m7V{&6W`_Lj2>3DZrLe6mRQy>x@-g6#)>CoL4Wc>c@T zs`35>OXF7uo;%FXzI-$NOv?O~5C4kG*o(~$G1}Gqaz}oe&L6W^w$t6#YfIgIe<?%w zTjY=2E8**WzAd)S6yehSAN@ol^+d7ZoC6};Rd{k|>?|)#)>Qvg-6{8VxnZ@z)9|SH z-4b<HWd<`l_C391T*dN2a9gfwRl{{v-ldN6n(tJr+Eja&IPynIbjk*rU0T2&4P~%* z{qC5@Jjw0Sg2n6hZE=~>Yw}{Rm|K!Nh;irhdfk?+3!L6d9M@Zy2IkK5kn(I6lU?H) z$op$z>%<#^smi>+mOrvv^xWN3IrGAI-JT<T8nZ>V*sgM{Pft`S3)$=`{C}R>zwHgt zhqkRf;@i^w`*ROZzQ|`GQ$M4Z%2TEPM9-dKC8!<~Z1QTc$eM|D-1~b^g*r?w4|?fz z?%V(4OCD`>s(I#mH2H74--Gaya~)R8#MX7W_H|d>_`P^)_l5Z{gI|lVmN4=&m#ov# z3h9VjGVPuInL{%#>6CmqBVHjByoLF`(7!jIbl-QkGPrr3zQ4y%+d!iGoOWthhg90n zycYs7uk}7O1*Nr09?|jPmu=HttF*@L)4{@3dtdt)Uu~RRW%%-g+Q-};qlL33D5ZBK zub%Jwfca_86-(c(^=@Vw55?@yN-Ulwej@bD*JVL_xbCRcR0N$3Zqlvo3OdY_b;_pc z&M_Xhru*vI240rtL6Ho7R&UA$ZW+$*nv&S{RWXhKb=h)TJ|FRAlkFedKe%rwUAq6p zuF0v3m^X_a4!y%yVrIx6bbsm5ZS(H;{yorsKsAB&cR~23WZ#DyR22AA|5i^>p0RYl z^Gv>keeZ1le3r<REnfdmBG~FTLvX>437UUxJ)jv_X`+Y64{5fRd7J+&E>mY<U|{fc L^>bP0l+XkKl~xvM literal 0 HcmV?d00001 diff --git a/src/AppDelegate.mm b/src/AppDelegate.mm index cee31395..134a457c 100644 --- a/src/AppDelegate.mm +++ b/src/AppDelegate.mm @@ -319,7 +319,7 @@ static void ReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNet - (void) showMainWindow { if(self.ringWindowController == nil) { - self.ringWindowController = [[RingWindowController alloc] initWithWindowNibName:@"RingWindow" bundle: nil accountModel:&lrc->getAccountModel() dataTransferModel:&lrc->getDataTransferModel() behaviourController:&lrc->getBehaviorController()]; + self.ringWindowController = [[RingWindowController alloc] initWithWindowNibName:@"RingWindow" bundle: nil accountModel:&lrc->getAccountModel() dataTransferModel:&lrc->getDataTransferModel() behaviourController:&lrc->getBehaviorController() avModel: &lrc->getAVModel()]; } [[NSApplication sharedApplication] removeWindowsItem:self.wizard.window]; [self.ringWindowController.window makeKeyAndOrderFront:self]; diff --git a/src/ConversationVC.h b/src/ConversationVC.h index c56b1009..c6e5860e 100644 --- a/src/ConversationVC.h +++ b/src/ConversationVC.h @@ -18,12 +18,19 @@ */ #import <Cocoa/Cocoa.h> -#import <api/conversation.h> -#import <api/conversationmodel.h> +#import <string> +namespace lrc { + namespace api { + class AVModel; + class ConversationModel; + } +} @class RingWindowController; +@class LeaveMessageVC; +@protocol LeaveMessageDelegate; -@interface ConversationVC : NSViewController +@interface ConversationVC : NSViewController <LeaveMessageDelegate> -(void) initFrame; -(void) showWithAnimation:(BOOL)animate; @@ -31,6 +38,9 @@ - (void) setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel*)model; -- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil delegate:(RingWindowController*) mainWindow; +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil delegate:(RingWindowController*) mainWindow aVModel:(lrc::api::AVModel*) avModel; +- (void) presentLeaveMessageView; + +-(NSViewController*) getMessagesView; @end diff --git a/src/ConversationVC.mm b/src/ConversationVC.mm index 9d2fe89c..f787e15c 100644 --- a/src/ConversationVC.mm +++ b/src/ConversationVC.mm @@ -36,8 +36,12 @@ #import "utils.h" #import "RingWindowController.h" #import "NSString+Extensions.h" +#import "LeaveMessageVC.h" +#import <QuickLook/QuickLook.h> +#import <Quartz/Quartz.h> +#import "LeaveMessageVC.h" -@interface ConversationVC () { +@interface ConversationVC () <QLPreviewPanelDataSource, QLPreviewPanelDelegate>{ __unsafe_unretained IBOutlet NSTextField* conversationTitle; __unsafe_unretained IBOutlet NSTextField *conversationID; @@ -46,6 +50,7 @@ __unsafe_unretained IBOutlet NSButton* sentContactRequestButton; IBOutlet MessagesVC* messagesViewVC; + LeaveMessageVC* leaveMessageVC; IBOutlet NSLayoutConstraint *titleCenteredConstraint; IBOutlet NSLayoutConstraint* titleTopConstraint; @@ -55,6 +60,7 @@ lrc::api::ConversationModel* convModel_; RingWindowController* delegate; + NSMutableArray* leaveMessageConversations; // All those connections are needed to invalidate cached conversation as pointer // may not be referencing the same conversation anymore @@ -69,15 +75,26 @@ NSInteger const SEND_PANEL_MAX_HEIGHT = 120; @implementation ConversationVC -- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil delegate:(RingWindowController*) mainWindow +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil delegate:(RingWindowController*) mainWindow aVModel:(lrc::api::AVModel*) avModel { if (self = [self initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { delegate = mainWindow; + leaveMessageVC = [[LeaveMessageVC alloc] initWithNibName:@"LeaveMessageVC" bundle:nil]; + [[leaveMessageVC view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [self.view addSubview:[leaveMessageVC view] positioned:NSWindowAbove relativeTo:nil]; + [leaveMessageVC initFrame]; + [leaveMessageVC setAVModel: avModel]; + leaveMessageConversations = [[NSMutableArray alloc] init]; + leaveMessageVC.delegate = self; } return self; } +-(NSViewController*) getMessagesView { + return messagesViewVC; +} + -(void) clearData { cachedConv_ = nil; convUid_ = ""; @@ -120,6 +137,11 @@ NSInteger const SEND_PANEL_MAX_HEIGHT = 120; if (convUid_.empty() || convModel_ == nil) return; + if([leaveMessageConversations containsObject:@(convUid_.c_str())]) { + [leaveMessageVC setConversationUID: convUid_ conversationModel: convModel_]; + } else { + [leaveMessageVC hide]; + } // Signals tracking changes in conversation list, we need them as cached conversation can be invalid // after a reordering. @@ -247,5 +269,13 @@ NSInteger const SEND_PANEL_MAX_HEIGHT = 120; [CATransaction commit]; } +- (void) presentLeaveMessageView { + [leaveMessageVC setConversationUID: convUid_ conversationModel: convModel_]; + [leaveMessageConversations addObject:@(convUid_.c_str())]; +} + +-(void) messageCompleted { + [leaveMessageConversations removeObject:@(convUid_.c_str())]; +} @end diff --git a/src/LeaveMessageVC.h b/src/LeaveMessageVC.h new file mode 100644 index 00000000..3ef6561d --- /dev/null +++ b/src/LeaveMessageVC.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018 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> +#import <string> + +namespace lrc { + namespace api { + class AVModel; + class ConversationModel; + } +} + +@protocol LeaveMessageDelegate + +-(void) messageCompleted; + +@end + +@interface LeaveMessageVC : NSViewController + +@property (retain, nonatomic) id <LeaveMessageDelegate> delegate; + +-(void)setConversationUID:(const std::string) convUid conversationModel:(lrc::api::ConversationModel*) convModel; +-(void) hide; +-(void) initFrame; +-(void) setAVModel: (const lrc::api::AVModel*) avmodel; + +@end + diff --git a/src/LeaveMessageVC.mm b/src/LeaveMessageVC.mm new file mode 100644 index 00000000..c35fcfcf --- /dev/null +++ b/src/LeaveMessageVC.mm @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2018 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 "LeaveMessageVC.h" +#import "views/NSColor+RingTheme.h" +#import "utils.h" + +//lrc +#import <api/avmodel.h> +#import <api/conversationmodel.h> + +#import <QuartzCore/QuartzCore.h> +#import "delegates/ImageManipulationDelegate.h" + +//Qt +#import <QtMacExtras/qmacfunctions.h> +#import <QPixmap> +#import <globalinstances.h> + +@interface LeaveMessageVC () { + __unsafe_unretained IBOutlet NSImageView* personPhoto; + __unsafe_unretained IBOutlet NSTextField* infoLabel; + __unsafe_unretained IBOutlet NSBox* timerBox; + __unsafe_unretained IBOutlet NSTextField* timerLabel; + __unsafe_unretained IBOutlet NSBox* sendBox; + __unsafe_unretained IBOutlet NSTextField* sendFilename; + __unsafe_unretained IBOutlet NSButton* recordButton; +} + +@end + +@implementation LeaveMessageVC + +bool isRecording = false; +int recordingTime = 0; +NSTimer* refreshDurationTimer; +lrc::api::AVModel* avModel; +std::string fileName; +NSMutableDictionary *filesToSend; +std::string conversationUid; +lrc::api::ConversationModel* conversationModel; + +- (void)loadView { + [super loadView]; + [personPhoto setWantsLayer:YES]; + personPhoto.layer.masksToBounds =true; + personPhoto.layer.cornerRadius = personPhoto.frame.size.width * 0.5; + filesToSend = [[NSMutableDictionary alloc] init]; +} + +-(void) setAVModel: (lrc::api::AVModel*) avmodel { + avModel = avmodel; +} + +-(void) initFrame { + [self.view setFrame:self.view.superview.bounds]; + [self.view setHidden:YES]; + self.view.layer.position = self.view.frame.origin; +} + +- (IBAction)cancel:(id)sender { + [self exit]; +} + +- (IBAction)recordMessage:(NSButton *)sender { + if (!isRecording) { + [self clearData]; + std::string file_name = avModel->startLocalRecorder(true); + if (file_name.empty()) { + return; + } + filesToSend[@(conversationUid.c_str())] = @(file_name.c_str()); + isRecording = true; + recordButton.image = [NSImage imageNamed:@"ic_stoprecord.png"]; + [timerBox setHidden:NO]; + if (refreshDurationTimer == nil) + refreshDurationTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 + target:self + selector:@selector(updateDurationLabel) + userInfo:nil + repeats:YES]; + } else { + avModel->stopLocalRecorder([filesToSend[@(conversationUid.c_str())] UTF8String]); + isRecording = false; + recordButton.image = [NSImage imageNamed:@"ic_action_audio.png"]; + [refreshDurationTimer invalidate]; + refreshDurationTimer = nil; + [timerBox setHidden:YES]; + [sendBox setHidden: NO]; + [sendFilename setStringValue:[self timeFormatted: recordingTime]]; + } +} + +- (IBAction)sendMessage:(NSButton *)sender { + NSArray* pathURL = [filesToSend[@(conversationUid.c_str())] componentsSeparatedByString: @"/"]; + if([pathURL count] < 1) { + return; + } + NSString* name = [pathURL objectAtIndex: [pathURL count] - 1]; + conversationModel->sendFile(conversationUid, [filesToSend[@(conversationUid.c_str())] UTF8String], [name UTF8String]); + [filesToSend removeObjectForKey: @(conversationUid.c_str())]; + [self exit]; +} + +- (void) exit { + [self clearData]; + [self hide]; + [self.delegate messageCompleted]; +} + +- (void)clearData { + recordButton.image = [NSImage imageNamed:@"ic_action_audio.png"]; + recordingTime = 0; + [timerLabel setStringValue: [self timeFormatted: recordingTime]]; + isRecording = false; + [timerBox setHidden:YES]; + [sendBox setHidden: YES]; + [refreshDurationTimer invalidate]; + refreshDurationTimer = nil; + [sendFilename setStringValue:@""]; + [filesToSend removeObjectForKey: @(conversationUid.c_str())]; +} + +- (void)viewWillHide { + recordButton.image = [NSImage imageNamed:@"ic_action_audio.png"]; + if(filesToSend[@(conversationUid.c_str())]) { + [sendFilename setStringValue:[self timeFormatted: recordingTime]]; + [sendBox setHidden: NO]; + } else { + [sendFilename setStringValue:@""]; + [sendBox setHidden: YES]; + } + recordingTime = 0; + [timerLabel setStringValue: [self timeFormatted: recordingTime]]; + isRecording = false; + [timerBox setHidden:YES]; + [refreshDurationTimer invalidate]; + refreshDurationTimer = nil; +} + +-(void) hide { + if(self.view.frame.origin.x < 0) { + return; + } + [self viewWillHide]; + [self.view setHidden:YES]; +} + +-(void) show { + if(self.view.frame.origin.x < 0) { + return; + } + [self.view setHidden:NO]; +} + +-(void)setConversationUID:(std::string) convUid conversationModel:(lrc::api::ConversationModel*) convModel { + conversationUid = convUid; + conversationModel = convModel; + [self updateView]; +} + +-(void) updateView { + auto it = getConversationFromUid(conversationUid, *conversationModel); + if (it != conversationModel->allFilteredConversations().end()) { + auto& imgManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator()); + QVariant photo = imgManip.conversationPhoto(*it, conversationModel->owner, QSize(120, 120), NO); + [personPhoto setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(photo))]; + NSString *name = bestNameForConversation(*it, *conversationModel); + [infoLabel setStringValue:name]; + } + [self show]; +} + +-(void) updateDurationLabel +{ + recordingTime++; + [timerLabel setStringValue: [self timeFormatted: recordingTime]]; +} + +- (NSString *)timeFormatted:(int)totalSeconds +{ + int seconds = totalSeconds % 60; + int minutes = (totalSeconds / 60) % 60; + return [NSString stringWithFormat:@"%02d:%02d",minutes, seconds]; +} + +@end diff --git a/src/LrcModelsProtocol.h b/src/LrcModelsProtocol.h index f75848e3..662f134c 100644 --- a/src/LrcModelsProtocol.h +++ b/src/LrcModelsProtocol.h @@ -22,6 +22,7 @@ namespace lrc { class DataTransferModel; class NewAccountModel; class BehaviorController; + class AVModel; } } @@ -30,9 +31,11 @@ namespace lrc { -(id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil dataTransferModel:(const lrc::api::DataTransferModel*) dataTransferModel; -(id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil accountmodel:(const lrc::api::NewAccountModel*) accountModel; -(id) initWithWindowNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil accountModel:(const lrc::api::NewAccountModel*)accountModel dataTransferModel:(const lrc::api::DataTransferModel*)dataTransferModel behaviourController:(const lrc::api::BehaviorController*) behaviorController; +-(id) initWithWindowNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil accountModel:(const lrc::api::NewAccountModel*)accountModel dataTransferModel:(const lrc::api::DataTransferModel*)dataTransferModel behaviourController:(const lrc::api::BehaviorController*) behaviorController avModel: (const lrc::api::AVModel*)avModel; @property lrc::api::DataTransferModel* dataTransferModel; @property lrc::api::NewAccountModel* accountModel; @property lrc::api::BehaviorController* behaviorController; +@property lrc::api::AVModel* avModel; @end diff --git a/src/MessagesVC.mm b/src/MessagesVC.mm index 90360b57..79ebee9d 100644 --- a/src/MessagesVC.mm +++ b/src/MessagesVC.mm @@ -50,7 +50,6 @@ std::string convUid_; lrc::api::ConversationModel* convModel_; const lrc::api::conversation::Info* cachedConv_; - QMetaObject::Connection newInteractionSignal_; // Both are needed to invalidate cached conversation as pointer @@ -790,6 +789,24 @@ typedef NS_ENUM(NSInteger, MessageSequencing) { return [dateFormatter stringFromDate:msgTime]; } +- (void) updateSendMessageHeight { + NSAttributedString *msgAttString = messageField.attributedStringValue; + NSRect frame = NSMakeRect(0, 0, messageField.frame.size.width, msgAttString.size.height); + NSTextView *tv = [[NSTextView alloc] initWithFrame:frame]; + [[tv textStorage] setAttributedString:msgAttString]; + [tv sizeToFit]; + CGFloat height = tv.frame.size.height + MEESAGE_MARGIN * 2; + CGFloat newHeight = MIN(SEND_PANEL_MAX_HEIGHT, MAX(SEND_PANEL_DEFAULT_HEIGHT, height)); + if(messagesBottomMargin.constant == newHeight) { + return; + } + messagesBottomMargin.constant = newHeight; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [self scrollToBottom]; + sendPanelHeight.constant = newHeight; + }); +} + #pragma mark - NSTableViewDataSource - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView @@ -877,39 +894,12 @@ typedef NS_ENUM(NSInteger, MessageSequencing) { if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) { [[QLPreviewPanel sharedPreviewPanel] orderOut:nil]; } else { - [[QLPreviewPanel sharedPreviewPanel] updateController]; - [QLPreviewPanel sharedPreviewPanel].dataSource = self; - [[QLPreviewPanel sharedPreviewPanel] setAnimationBehavior:NSWindowAnimationBehaviorDocumentWindow]; - [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:self]; + }); } } -- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel { - return 1; -} - -- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index { - return [NSURL fileURLWithPath:previewImage]; -} - -- (void) updateSendMessageHeight { - NSAttributedString *msgAttString = messageField.attributedStringValue; - NSRect frame = NSMakeRect(0, 0, messageField.frame.size.width, msgAttString.size.height); - NSTextView *tv = [[NSTextView alloc] initWithFrame:frame]; - [[tv textStorage] setAttributedString:msgAttString]; - [tv sizeToFit]; - CGFloat height = tv.frame.size.height + MEESAGE_MARGIN * 2; - CGFloat newHeight = MIN(SEND_PANEL_MAX_HEIGHT, MAX(SEND_PANEL_DEFAULT_HEIGHT, height)); - if(messagesBottomMargin.constant == newHeight) { - return; - } - messagesBottomMargin.constant = newHeight; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [self scrollToBottom]; - sendPanelHeight.constant = newHeight; - }); -} - - (IBAction)sendMessage:(id)sender { NSString* text = self.message; if (text && text.length > 0) { @@ -971,4 +961,28 @@ typedef NS_ENUM(NSInteger, MessageSequencing) { [self updateSendMessageHeight]; } +#pragma mark - QLPreviewPanelDataSource + +-(void)beginPreviewPanelControl:(QLPreviewPanel *)panel +{ + panel.dataSource = self; +} + +- (void)endPreviewPanelControl:(QLPreviewPanel *)panel { + panel.dataSource = nil; +} + +-(BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel +{ + return YES; +} + +- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel { + return 1; +} + +- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index { + return [NSURL fileURLWithPath:previewImage]; +} + @end diff --git a/src/RingWindowController.mm b/src/RingWindowController.mm index 971622e4..dbfd0972 100644 --- a/src/RingWindowController.mm +++ b/src/RingWindowController.mm @@ -59,6 +59,7 @@ typedef NS_ENUM(NSInteger, ViewState) { SHOW_CONVERSATION_SCREEN, SHOW_CALL_SCREEN, SHOW_SETTINGS_SCREEN, + LEAVE_MESSAGE, }; @interface RingWindowController () <MigrateRingAccountsDelegate> @@ -87,24 +88,20 @@ typedef NS_ENUM(NSInteger, ViewState) { ConversationVC* conversationVC; AccountSettingsVC* settingsVC; - // toolbar menu items IBOutlet ChooseAccountVC* chooseAccountVC; } -static NSString* const kPreferencesIdentifier = @"PreferencesIdentifier"; -NSString* const kChangeAccountToolBarItemIdentifier = @"ChangeAccountToolBarItemIdentifier"; -NSString* const kOpenAccountToolBarItemIdentifier = @"OpenAccountToolBarItemIdentifier"; - -@synthesize dataTransferModel, accountModel, behaviorController; +@synthesize dataTransferModel, accountModel, behaviorController, avModel; @synthesize wizard; --(id) initWithWindowNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil accountModel:( lrc::api::NewAccountModel*)accountModel dataTransferModel:( lrc::api::DataTransferModel*)dataTransferModel behaviourController:( lrc::api::BehaviorController*) behaviorController +-(id) initWithWindowNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil accountModel:( lrc::api::NewAccountModel*)accountModel dataTransferModel:( lrc::api::DataTransferModel*)dataTransferModel behaviourController:( lrc::api::BehaviorController*) behaviorController avModel: (lrc::api::AVModel*)avModel { if (self = [self initWithWindowNibName:nibNameOrNil]) { self.accountModel = accountModel; self.dataTransferModel = dataTransferModel; self.behaviorController = behaviorController; + self.avModel = avModel; } return self; } @@ -165,6 +162,10 @@ NSString* const kOpenAccountToolBarItemIdentifier = @"OpenAccountToolBarItemI [smartViewVC.view setHidden: YES]; [settingsVC show]; break; + case LEAVE_MESSAGE: + [conversationVC showWithAnimation: false]; + [currentCallVC hideWithAnimation: false]; + [conversationVC presentLeaveMessageView]; default: break; } @@ -178,12 +179,9 @@ NSString* const kOpenAccountToolBarItemIdentifier = @"OpenAccountToolBarItemI currentCallVC = [[CurrentCallVC alloc] initWithNibName:@"CurrentCall" bundle:nil]; currentCallVC.delegate = self; - conversationVC = [[ConversationVC alloc] initWithNibName:@"Conversation" bundle:nil delegate:self]; - // toolbar items - //chooseAccountVC = [[ChooseAccountVC alloc] initWithNibName:@"ChooseAccount" bundle:nil model:self.accountModel delegate:self]; + conversationVC = [[ConversationVC alloc] initWithNibName:@"Conversation" bundle:nil delegate:self aVModel:self.avModel]; [chooseAccountVC updateWithDelegate: self andModel:self.accountModel]; settingsVC = [[AccountSettingsVC alloc] initWithNibName:@"AccountSettings" bundle:nil accountmodel:self.accountModel]; - //[self.window.contentView addSubview:[chooseAccountVC view]]; [callView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; [[currentCallVC view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; [[conversationVC view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; @@ -219,6 +217,11 @@ NSString* const kOpenAccountToolBarItemIdentifier = @"OpenAccountToolBarItemI } NSToolbar *tb = [[self window] toolbar]; [tb setAllowsUserCustomization:NO]; + + //add messages view controller to responders chain + NSResponder * viewNextResponder = [self nextResponder]; + [self setNextResponder: [conversationVC getMessagesView]]; + [[conversationVC getMessagesView] setNextResponder: viewNextResponder]; } - (void) connect @@ -266,6 +269,15 @@ NSString* const kOpenAccountToolBarItemIdentifier = @"OpenAccountToolBarItemI [smartViewVC selectConversation: convInfo model:accInfo.conversationModel.get()]; [self changeViewTo:SHOW_CONVERSATION_SCREEN]; }); + QObject::connect(self.behaviorController, + &lrc::api::BehaviorController::showLeaveMessageView, + [self](const std::string& accountId, + const lrc::api::conversation::Info& convInfo){ + auto& accInfo = self.accountModel->getAccountInfo(accountId); + [conversationVC setConversationUid:convInfo.uid model:accInfo.conversationModel.get()]; + [smartViewVC selectConversation: convInfo model:accInfo.conversationModel.get()]; + [self changeViewTo:LEAVE_MESSAGE]; + }); } /** diff --git a/ui/Base.lproj/LeaveMessageVC.xib b/ui/Base.lproj/LeaveMessageVC.xib new file mode 100644 index 00000000..783ba042 --- /dev/null +++ b/ui/Base.lproj/LeaveMessageVC.xib @@ -0,0 +1,257 @@ +<?xml version="1.0" encoding="UTF-8"?> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> + <dependencies> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/> + <capability name="System colors introduced in macOS 10.14" minToolsVersion="10.0"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> + </dependencies> + <objects> + <customObject id="-2" userLabel="File's Owner" customClass="LeaveMessageVC"> + <connections> + <outlet property="infoLabel" destination="oeJ-VI-uhE" id="9bG-8v-9PT"/> + <outlet property="personPhoto" destination="gFu-2j-j1g" id="NOO-yO-d5d"/> + <outlet property="recordButton" destination="cOl-pS-cP5" id="YsL-fB-biO"/> + <outlet property="sendBox" destination="MxH-aj-EXO" id="715-t9-z7D"/> + <outlet property="sendFilename" destination="Zj8-Dk-VI5" id="y15-gg-zgb"/> + <outlet property="timerBox" destination="WtB-WH-CJz" id="WyV-9R-KT0"/> + <outlet property="timerLabel" destination="1k9-gR-Qgh" id="Rch-wt-Q7U"/> + <outlet property="view" destination="Hz6-mo-xeY" id="0ae-8W-gNO"/> + </connections> + </customObject> + <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> + <customObject id="-3" userLabel="Application" customClass="NSObject"/> + <customView id="Hz6-mo-xeY"> + <rect key="frame" x="0.0" y="0.0" width="798" height="658"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <subviews> + <box boxType="custom" borderType="none" cornerRadius="4" title="Box" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="TwL-yO-BXI"> + <rect key="frame" x="0.0" y="0.0" width="798" height="658"/> + <view key="contentView" id="RNM-sD-Phj"> + <rect key="frame" x="0.0" y="0.0" width="798" height="658"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + </view> + <color key="borderColor" name="windowBackgroundColor" catalog="System" colorSpace="catalog"/> + <color key="fillColor" name="windowBackgroundColor" catalog="System" colorSpace="catalog"/> + </box> + <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="cOl-pS-cP5" customClass="HoverButton"> + <rect key="frame" x="350" y="222" width="40" height="40"/> + <constraints> + <constraint firstAttribute="width" constant="40" id="hiL-Mx-vuA"/> + <constraint firstAttribute="height" constant="40" id="qPN-3f-Ko7"/> + </constraints> + <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="ic_action_audio" imagePosition="only" alignment="center" transparent="YES" imageScaling="proportionallyUpOrDown" inset="2" id="hiW-Yq-71M"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <userDefinedRuntimeAttributes> + <userDefinedRuntimeAttribute type="number" keyPath="imageInsets"> + <integer key="value" value="6"/> + </userDefinedRuntimeAttribute> + <userDefinedRuntimeAttribute type="color" keyPath="hoverColor"> + <color key="value" name="unemphasizedSelectedContentBackgroundColor" catalog="System" colorSpace="catalog"/> + </userDefinedRuntimeAttribute> + <userDefinedRuntimeAttribute type="color" keyPath="imageColor"> + <color key="value" name="labelColor" catalog="System" colorSpace="catalog"/> + </userDefinedRuntimeAttribute> + </userDefinedRuntimeAttributes> + <connections> + <action selector="recordMessage:" target="-2" id="AcE-mM-mr5"/> + </connections> + </button> + <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0v7-mC-auu" customClass="HoverButton"> + <rect key="frame" x="400" y="222" width="40" height="40"/> + <constraints> + <constraint firstAttribute="height" constant="40" id="Ubr-IA-SO6"/> + <constraint firstAttribute="width" constant="40" id="hhA-GJ-SQo"/> + </constraints> + <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="ic_exit" imagePosition="only" alignment="center" transparent="YES" imageScaling="proportionallyUpOrDown" inset="2" id="121-P0-aCH"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <userDefinedRuntimeAttributes> + <userDefinedRuntimeAttribute type="number" keyPath="imageInsets"> + <integer key="value" value="6"/> + </userDefinedRuntimeAttribute> + <userDefinedRuntimeAttribute type="color" keyPath="hoverColor"> + <color key="value" name="unemphasizedSelectedContentBackgroundColor" catalog="System" colorSpace="catalog"/> + </userDefinedRuntimeAttribute> + <userDefinedRuntimeAttribute type="color" keyPath="imageColor"> + <color key="value" name="labelColor" catalog="System" colorSpace="catalog"/> + </userDefinedRuntimeAttribute> + </userDefinedRuntimeAttributes> + <connections> + <action selector="cancel:" target="-2" id="dVf-0K-pdq"/> + </connections> + </button> + <imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="gFu-2j-j1g"> + <rect key="frame" x="339" y="398" width="120" height="120"/> + <constraints> + <constraint firstAttribute="height" constant="120" id="KMx-gW-5Jr"/> + <constraint firstAttribute="width" constant="120" id="kGL-D8-QE0"/> + </constraints> + <imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSUser" id="boQ-fa-nNO"/> + </imageView> + <box titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="WtB-WH-CJz"> + <rect key="frame" x="306" y="118" width="187" height="86"/> + <view key="contentView" id="tbn-BB-CbR"> + <rect key="frame" x="3" y="3" width="181" height="80"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="CvL-tU-YNA"> + <rect key="frame" x="57" y="53" width="66" height="17"/> + <textFieldCell key="cell" lineBreakMode="clipping" title="Recording" id="kIq-PG-Ymw"> + <font key="font" usesAppearanceFont="YES"/> + <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="1k9-gR-Qgh"> + <rect key="frame" x="48" y="8" width="84" height="35"/> + <textFieldCell key="cell" lineBreakMode="clipping" title="00:00" id="78U-cA-2f2"> + <font key="font" metaFont="system" size="29"/> + <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + </subviews> + <constraints> + <constraint firstItem="1k9-gR-Qgh" firstAttribute="centerX" secondItem="tbn-BB-CbR" secondAttribute="centerX" id="ALD-VB-KGO"/> + <constraint firstItem="CvL-tU-YNA" firstAttribute="top" secondItem="tbn-BB-CbR" secondAttribute="top" constant="10" id="Xxf-0h-14a"/> + <constraint firstItem="CvL-tU-YNA" firstAttribute="centerX" secondItem="tbn-BB-CbR" secondAttribute="centerX" id="Ylf-xL-G77"/> + <constraint firstItem="1k9-gR-Qgh" firstAttribute="top" secondItem="CvL-tU-YNA" secondAttribute="bottom" constant="10" id="gnT-ir-ivn"/> + </constraints> + </view> + <constraints> + <constraint firstAttribute="width" constant="181" id="Wgv-cO-Eg5"/> + <constraint firstAttribute="height" constant="80" id="dEB-5M-hAm"/> + </constraints> + <font key="titleFont" metaFont="cellTitle"/> + </box> + <box title="Box" titlePosition="noTitle" transparent="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MxH-aj-EXO"> + <rect key="frame" x="306" y="118" width="187" height="86"/> + <view key="contentView" id="U9z-Rg-Zs3"> + <rect key="frame" x="3" y="3" width="181" height="80"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <box boxType="custom" borderType="bezel" cornerRadius="20" title="Box" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="Zlv-nH-Y9y"> + <rect key="frame" x="20" y="40" width="100" height="40"/> + <view key="contentView" id="bxd-ht-hMm"> + <rect key="frame" x="1" y="1" width="98" height="38"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="kSA-Cr-Pp3"> + <rect key="frame" x="10" y="10" width="20" height="18"/> + <constraints> + <constraint firstAttribute="width" constant="20" id="YUW-eF-aaS"/> + </constraints> + <imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="ic_audio_file" id="8ee-Km-Vp6"/> + </imageView> + <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Zj8-Dk-VI5"> + <rect key="frame" x="38" y="11" width="62" height="17"/> + <textFieldCell key="cell" lineBreakMode="clipping" id="Lml-tH-Au6"> + <font key="font" metaFont="system"/> + <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + </subviews> + <constraints> + <constraint firstItem="Zj8-Dk-VI5" firstAttribute="centerY" secondItem="bxd-ht-hMm" secondAttribute="centerY" id="Hpd-t9-NNA"/> + <constraint firstItem="kSA-Cr-Pp3" firstAttribute="centerY" secondItem="bxd-ht-hMm" secondAttribute="centerY" id="c4e-NF-hy4"/> + <constraint firstAttribute="trailing" secondItem="Zj8-Dk-VI5" secondAttribute="trailing" id="iWD-1w-N3k"/> + <constraint firstItem="kSA-Cr-Pp3" firstAttribute="leading" secondItem="bxd-ht-hMm" secondAttribute="leading" constant="10" id="ofV-eC-JpI"/> + <constraint firstItem="kSA-Cr-Pp3" firstAttribute="top" secondItem="bxd-ht-hMm" secondAttribute="top" constant="10" id="x9O-tw-81d"/> + <constraint firstItem="Zj8-Dk-VI5" firstAttribute="leading" secondItem="kSA-Cr-Pp3" secondAttribute="trailing" constant="10" id="zu8-7j-OP5"/> + </constraints> + </view> + <constraints> + <constraint firstAttribute="height" constant="40" id="Iyu-Nl-TpG"/> + <constraint firstAttribute="width" constant="100" id="cw3-NU-2Lj"/> + </constraints> + <color key="borderColor" name="unemphasizedSelectedContentBackgroundColor" catalog="System" colorSpace="catalog"/> + <color key="fillColor" name="unemphasizedSelectedContentBackgroundColor" catalog="System" colorSpace="catalog"/> + </box> + <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="XN4-Vb-UWd" customClass="HoverButton"> + <rect key="frame" x="131" y="40" width="40" height="40"/> + <constraints> + <constraint firstAttribute="height" constant="40" id="LNf-7D-co7"/> + <constraint firstAttribute="width" constant="40" id="ouZ-f5-Vfs"/> + </constraints> + <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="ic_action_send" imagePosition="only" alignment="center" transparent="YES" imageScaling="proportionallyDown" inset="2" id="9LO-UF-tss"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <userDefinedRuntimeAttributes> + <userDefinedRuntimeAttribute type="number" keyPath="imageInsets"> + <integer key="value" value="6"/> + </userDefinedRuntimeAttribute> + <userDefinedRuntimeAttribute type="color" keyPath="imageColor"> + <color key="value" name="labelColor" catalog="System" colorSpace="catalog"/> + </userDefinedRuntimeAttribute> + <userDefinedRuntimeAttribute type="color" keyPath="hoverColor"> + <color key="value" name="unemphasizedSelectedContentBackgroundColor" catalog="System" colorSpace="catalog"/> + </userDefinedRuntimeAttribute> + </userDefinedRuntimeAttributes> + <connections> + <action selector="sendMessage:" target="-2" id="4WL-7f-xpJ"/> + </connections> + </button> + </subviews> + <constraints> + <constraint firstItem="XN4-Vb-UWd" firstAttribute="top" secondItem="U9z-Rg-Zs3" secondAttribute="top" id="073-sZ-trD"/> + <constraint firstItem="Zlv-nH-Y9y" firstAttribute="leading" secondItem="U9z-Rg-Zs3" secondAttribute="leading" constant="20" id="Xh6-x6-TnF"/> + <constraint firstItem="Zlv-nH-Y9y" firstAttribute="top" secondItem="U9z-Rg-Zs3" secondAttribute="top" id="aot-Tv-9mi"/> + <constraint firstAttribute="trailing" secondItem="XN4-Vb-UWd" secondAttribute="trailing" constant="10" id="hUi-OE-cZU"/> + <constraint firstItem="XN4-Vb-UWd" firstAttribute="leading" secondItem="Zlv-nH-Y9y" secondAttribute="trailing" constant="11" id="j7u-cW-AWB"/> + </constraints> + </view> + </box> + <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oeJ-VI-uhE"> + <rect key="frame" x="378" y="320" width="42" height="18"/> + <textFieldCell key="cell" lineBreakMode="clipping" title="Name" id="uol-7p-Y2J"> + <font key="font" metaFont="system" size="14"/> + <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dbY-HE-70Z"> + <rect key="frame" x="247" y="282" width="304" height="18"/> + <textFieldCell key="cell" lineBreakMode="clipping" title="Contact appears to be busy. Leave a message?" id="hnr-x5-xnb"> + <font key="font" metaFont="systemLight" size="14"/> + <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + </subviews> + <constraints> + <constraint firstItem="cOl-pS-cP5" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" constant="-29" id="3kZ-xx-zfF"/> + <constraint firstItem="WtB-WH-CJz" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" id="4ia-YI-y5O"/> + <constraint firstItem="dbY-HE-70Z" firstAttribute="top" secondItem="oeJ-VI-uhE" secondAttribute="bottom" constant="20" id="725-UY-bsG"/> + <constraint firstItem="WtB-WH-CJz" firstAttribute="leading" secondItem="MxH-aj-EXO" secondAttribute="leading" id="DbY-VH-imQ"/> + <constraint firstItem="WtB-WH-CJz" firstAttribute="trailing" secondItem="MxH-aj-EXO" secondAttribute="trailing" id="E7l-KF-ph3"/> + <constraint firstAttribute="trailing" secondItem="TwL-yO-BXI" secondAttribute="trailing" id="Hi0-k1-dx9"/> + <constraint firstItem="TwL-yO-BXI" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="Hle-nR-C5u"/> + <constraint firstItem="oeJ-VI-uhE" firstAttribute="centerY" secondItem="Hz6-mo-xeY" secondAttribute="centerY" id="IBz-Qp-LpV"/> + <constraint firstItem="TwL-yO-BXI" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" id="LrF-0X-nHd"/> + <constraint firstItem="gFu-2j-j1g" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" id="MnV-YG-76G"/> + <constraint firstItem="WtB-WH-CJz" firstAttribute="bottom" secondItem="MxH-aj-EXO" secondAttribute="bottom" id="XO6-7p-v1a"/> + <constraint firstAttribute="bottom" secondItem="TwL-yO-BXI" secondAttribute="bottom" id="Yhm-GI-18S"/> + <constraint firstItem="WtB-WH-CJz" firstAttribute="top" secondItem="cOl-pS-cP5" secondAttribute="bottom" constant="20" id="ZlU-DX-nL3"/> + <constraint firstItem="0v7-mC-auu" firstAttribute="leading" secondItem="cOl-pS-cP5" secondAttribute="trailing" constant="10" id="afB-FF-Op0"/> + <constraint firstItem="cOl-pS-cP5" firstAttribute="top" secondItem="dbY-HE-70Z" secondAttribute="bottom" constant="20" id="bNl-00-Thl"/> + <constraint firstItem="0v7-mC-auu" firstAttribute="centerY" secondItem="cOl-pS-cP5" secondAttribute="centerY" id="hGt-wT-rrj"/> + <constraint firstItem="oeJ-VI-uhE" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" id="l8d-MQ-RwK"/> + <constraint firstItem="dbY-HE-70Z" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" id="orz-1K-VBk"/> + <constraint firstItem="oeJ-VI-uhE" firstAttribute="top" secondItem="gFu-2j-j1g" secondAttribute="bottom" constant="60" id="rlm-H5-Lp8"/> + <constraint firstItem="WtB-WH-CJz" firstAttribute="top" secondItem="MxH-aj-EXO" secondAttribute="top" id="vuA-zF-FBa"/> + </constraints> + </customView> + </objects> + <resources> + <image name="NSUser" width="32" height="32"/> + <image name="ic_action_audio" width="36" height="36"/> + <image name="ic_action_send" width="72" height="72"/> + <image name="ic_audio_file" width="75" height="75"/> + <image name="ic_exit" width="64" height="64"/> + </resources> +</document> -- GitLab