diff --git a/Classes/AppManager.swift b/Classes/AppManager.swift index d1987e746..9a86a02e9 100644 --- a/Classes/AppManager.swift +++ b/Classes/AppManager.swift @@ -64,4 +64,16 @@ enum NetworkType: Int { //The recording prefix is used to identify recordings in the cache directory. //We will use name_dayName-day-monthName-year to separate recordings by days, then hour-minutes-seconds to order them in each day. } + + @objc static func removeFile(file: String) { + let fileManager = FileManager.default + do { + try fileManager.removeItem(atPath: file) + Log.directLog(BCTBX_LOG_MESSAGE, text: "File :\(file) removed") + + } catch { + print("Could not remove file : \(file) \(error)") + } + } + } diff --git a/Classes/Base.lproj/ChatConversationImdnView.xib b/Classes/Base.lproj/ChatConversationImdnView.xib index c768ffd1d..99dc77538 100644 --- a/Classes/Base.lproj/ChatConversationImdnView.xib +++ b/Classes/Base.lproj/ChatConversationImdnView.xib @@ -1,17 +1,15 @@ - + + - + + + - - - - - @@ -19,19 +17,19 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + @@ -112,10 +76,14 @@ - - + + + + + + diff --git a/Classes/Base.lproj/ChatConversationView.xib b/Classes/Base.lproj/ChatConversationView.xib index e321384a1..446b483da 100644 --- a/Classes/Base.lproj/ChatConversationView.xib +++ b/Classes/Base.lproj/ChatConversationView.xib @@ -31,12 +31,22 @@ + + + + + + + + + + @@ -209,7 +219,7 @@ - + @@ -221,7 +231,7 @@ - + + @@ -313,6 +388,11 @@ + - + - + - + + - + - + - + @@ -146,31 +165,39 @@ + - + @@ -190,6 +217,7 @@ + @@ -198,5 +226,11 @@ + + + + + + diff --git a/Classes/CallManager.swift b/Classes/CallManager.swift index f6458e9cc..93e998212 100644 --- a/Classes/CallManager.swift +++ b/Classes/CallManager.swift @@ -651,9 +651,31 @@ import AVFoundation } } + // Audio messages + + @objc func activateAudioSession() { + lc?.activateAudioSession(actived: true) + } + + @objc func getSpeakerSoundCard() -> String? { + var speakerCard: String? = nil + var earpieceCard: String? = nil + lc?.audioDevices.forEach { device in + if (device.hasCapability(capability: .CapabilityPlay)) { + if (device.type == .Speaker) { + speakerCard = device.id + } else if (device.type == .Earpiece) { + earpieceCard = device.id + } + } + } + return speakerCard != nil ? speakerCard : earpieceCard + } + + // Conference - + @objc func hostConference() -> Bool { return conference != nil } @@ -748,7 +770,9 @@ import AVFoundation guard let core = lc else { return false } - return isInConference() && (getConference()?.currentParams?.isVideoEnabled == true || core.currentCall?.currentParams?.videoEnabled == true) + let result = isInConference() && (getConference()?.currentParams?.isVideoEnabled == true || core.currentCall?.currentParams?.videoEnabled == true) + NSLog("cdes \(result) \(core.currentCall?.currentParams?.videoEnabled)") + return result } diff --git a/Classes/CallView.m b/Classes/CallView.m index 712ebffc6..2ae10de87 100644 --- a/Classes/CallView.m +++ b/Classes/CallView.m @@ -683,9 +683,8 @@ static void hideSpinner(LinphoneCall *call, void *user_data) { #pragma mark - ActionSheet Functions - (void)displayAskToEnableVideoCall:(LinphoneCall *)call { - if (linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call))) { - return; - } else if (CallManager.instance.inVideoConf) { + + if (CallManager.instance.inVideoConf) { // we are hosting a video conf, so just accept people wanting to activate video. LinphoneCallParams *params = linphone_core_create_call_params(LC, call); linphone_call_params_enable_video(params, TRUE); linphone_call_accept_update(call, params); diff --git a/Classes/ChatConversationImdnView.h b/Classes/ChatConversationImdnView.h index 67d6d820b..78a9042b6 100644 --- a/Classes/ChatConversationImdnView.h +++ b/Classes/ChatConversationImdnView.h @@ -36,13 +36,11 @@ @property(nonatomic) bctbx_list_t *receivedList; @property(nonatomic) bctbx_list_t *notReceivedList; @property(nonatomic) bctbx_list_t *errorList; +@property(nonatomic) UIChatBubbleTextCell *cell; +@property(nonatomic) NSTimer *ephemeralDisplayTimer; + @property (weak, nonatomic) IBOutlet UIView *msgView; -@property (weak, nonatomic) IBOutlet UIImageView *msgBackgroundColorImage; -@property (weak, nonatomic) IBOutlet UIRoundedImageView *msgAvatarImage; -@property (weak, nonatomic) IBOutlet UIImageView *msgBottomBar; -@property (weak, nonatomic) IBOutlet UILabel *msgDateLabel; -@property (weak, nonatomic) IBOutlet UITextViewNoDefine *msgText; @property (weak, nonatomic) IBOutlet UITableView *tableView; - (IBAction)onBackClick:(id)sender; diff --git a/Classes/ChatConversationImdnView.m b/Classes/ChatConversationImdnView.m index 38ceeb337..c99f9859a 100644 --- a/Classes/ChatConversationImdnView.m +++ b/Classes/ChatConversationImdnView.m @@ -22,6 +22,7 @@ #import "ChatConversationImdnView.h" #import "PhoneMainView.h" #import "UIChatBubbleTextCell.h" +#import "UIChatBubblePhotoCell.h" #import "UIChatConversationImdnTableViewCell.h" @implementation ChatConversationImdnView @@ -52,21 +53,40 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - const LinphoneAddress *addr = linphone_chat_message_get_from_address(_msg); - BOOL outgoing = linphone_chat_message_is_outgoing(_msg); - - _msgDateLabel.text = [NSString stringWithFormat:@"%@ - %@", - [LinphoneUtils timeToString:linphone_chat_message_get_time(_msg) withFormat:LinphoneDateChatBubble], - [FastAddressBook displayNameForAddress:addr]]; - _msgAvatarImage.image = outgoing ? [LinphoneUtils selfAvatar] : [FastAddressBook imageForAddress:addr]; - _msgText.text = messageText; - _msgBackgroundColorImage.image = _msgBottomBar.image = [UIImage imageNamed:(outgoing ? @"color_A.png" : @"color_D.png")]; - _msgDateLabel.textColor = [UIColor colorWithPatternImage:_msgBackgroundColorImage.image]; - + + int index = [VIEW(ChatConversationView).tableController indexOfMesssage:_msg]; + if (index < 0) + [PhoneMainView.instance popToView:ChatConversationView.compositeViewDescription]; + + _cell = (UIChatBubbleTextCell *)[VIEW(ChatConversationView).tableController tableView:VIEW(ChatConversationView).tableController.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]]; + _cell.frame = CGRectMake(-10,0,_msgView.frame.size.width,_msgView.frame.size.height); + _cell.isFirst = true; + _cell.isLast = true; + [_cell update]; + _cell.popupMenuAllowed = false; + for (UIView *v in [_msgView subviews]) { + [v removeFromSuperview]; + } + [_msgView addSubview:_cell]; + + _tableView.delegate = self; _tableView.dataSource = self; [self updateImdnList]; + [self fitContent]; + [self startEphemeralDisplayTimer]; + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(ephemeralDeleted:) + name:kLinphoneEphemeralMessageDeletedInRoom + object:nil]; + +} + +-(void) viewWillDisappear:(BOOL)animated { + [self stopEphemeralDisplayTimer]; + [NSNotificationCenter.defaultCenter removeObserver:self]; + [super viewWillDisappear:animated]; } - (void)updateImdnList { @@ -81,15 +101,12 @@ static UICompositeViewDescription *compositeDescription = nil; } - (void)fitContent { - [self setMessageText]; - - BOOL outgoing = linphone_chat_message_is_outgoing(_msg); - _msgBackgroundColorImage.image = _msgBottomBar.image = [UIImage imageNamed:(outgoing ? @"color_A.png" : @"color_D.png")]; - _msgDateLabel.textColor = [UIColor colorWithPatternImage:_msgBackgroundColorImage.image]; + + CGSize messageSize = [UIChatBubbleTextCell ViewHeightForMessage:_msg withWidth:self.view.frame.size.width]; [_msgView setFrame:CGRectMake(_msgView.frame.origin.x, _msgView.frame.origin.y, - _msgView.frame.size.width, - [UIChatBubbleTextCell ViewHeightForMessageText:_msg withWidth:self.view.frame.size.width textForImdn:messageText].height)]; + self.view.frame.size.width, + messageSize.height+5)]; [_tableView setFrame:CGRectMake(_tableView.frame.origin.x, _msgView.frame.origin.y + _msgView.frame.size.height + 10, @@ -101,18 +118,7 @@ static UICompositeViewDescription *compositeDescription = nil; [self fitContent]; } -- (void)setMessageText { - const char *utf8Text= linphone_chat_message_get_text_content(_msg); - LinphoneContent *fileContent = linphone_chat_message_get_file_transfer_information(_msg); - messageText = nil; - if (utf8Text) { - messageText = [NSString stringWithUTF8String:utf8Text]; - if (fileContent) - messageText = [NSString stringWithFormat:@"%@\n%@", messageText, [NSString stringWithUTF8String: linphone_content_get_name(fileContent)]]; - } else { - messageText = [NSString stringWithUTF8String: linphone_content_get_name(fileContent)]; - } -} + #pragma mark - TableView - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { @@ -278,4 +284,38 @@ static UICompositeViewDescription *compositeDescription = nil; [PhoneMainView.instance popCurrentView]; } +#pragma mark ephemeral messages + +-(void) startEphemeralDisplayTimer { + _ephemeralDisplayTimer = [NSTimer scheduledTimerWithTimeInterval:1 + target:self + selector:@selector(updateEphemeralTimes) + userInfo:nil + repeats:YES]; +} + +-(void) updateEphemeralTimes { + NSDateComponentsFormatter *f= [[NSDateComponentsFormatter alloc] init]; + f.unitsStyle = NSDateComponentsFormatterUnitsStylePositional; + f.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad; + + if (linphone_chat_message_is_ephemeral(_msg)) { + long duration = linphone_chat_message_get_ephemeral_expire_time(_msg) == 0 ? + linphone_chat_room_get_ephemeral_lifetime(linphone_chat_message_get_chat_room(_msg)) : + linphone_chat_message_get_ephemeral_expire_time(_msg)-[NSDate date].timeIntervalSince1970; + f.allowedUnits = (duration > 86400 ? kCFCalendarUnitDay : 0)|(duration > 3600 ? kCFCalendarUnitHour : 0)|kCFCalendarUnitMinute|kCFCalendarUnitSecond; + _cell.ephemeralTime.text = [f stringFromTimeInterval:duration]; + _cell.ephemeralTime.hidden = NO; + _cell.ephemeralIcon.hidden = NO; + } +} + +-(void) stopEphemeralDisplayTimer { + [_ephemeralDisplayTimer invalidate]; +} + +- (void)ephemeralDeleted:(NSNotification *)notif { + [PhoneMainView.instance popToView:ChatConversationView.compositeViewDescription]; +} + @end diff --git a/Classes/ChatConversationTableView.h b/Classes/ChatConversationTableView.h index 3fb5e83be..1fe9ff3e2 100644 --- a/Classes/ChatConversationTableView.h +++ b/Classes/ChatConversationTableView.h @@ -39,10 +39,10 @@ @protocol ChatConversationDelegate -- (BOOL)resendMultiFiles:(FileContext *)newFileContext message:(NSString *)message; -- (BOOL)resendFile:(NSData *)data withName:(NSString *)name type:(NSString *)type key:(NSString *)key message:(NSString *)message; -- (BOOL)startFileUpload:(NSData *)data withName:(NSString *)name; -- (void)resendChat:(NSString *)message withExternalUrl:(NSString *)url; +- (BOOL)resendMultiFiles:(FileContext *)newFileContext message:(NSString *)message rootMessage:(LinphoneChatMessage *)rootMessage; +- (BOOL)resendFile:(NSData *)data withName:(NSString *)name type:(NSString *)type key:(NSString *)key message:(NSString *)message rootMessage:(LinphoneChatMessage *)rootMessage; +- (BOOL)startFileUpload:(NSData *)data withName:(NSString *)name rootMessage:(LinphoneChatMessage *)rootMessage; +- (void)resendChat:(NSString *)message withExternalUrl:(NSString *)url rootMessage:(LinphoneChatMessage *)rootMessage; - (void)tableViewIsScrolling; @end @@ -64,5 +64,9 @@ - (void)scrollToLastUnread:(BOOL)animated; - (void)updateEventEntry:(LinphoneEventLog *)event; - (void)refreshData; +- (void)reloadData; +- (void) dismissMessagesPopups; +- (void) scrollToMessage:(LinphoneChatMessage *)message; +- (int) indexOfMesssage:(LinphoneChatMessage *)message; @end diff --git a/Classes/ChatConversationTableView.m b/Classes/ChatConversationTableView.m index 026115094..45b44367d 100644 --- a/Classes/ChatConversationTableView.m +++ b/Classes/ChatConversationTableView.m @@ -49,6 +49,7 @@ } -(void) viewWillDisappear:(BOOL)animated { + [self dismissMessagesPopups]; [self stopEphemeralDisplayTimer]; [NSNotificationCenter.defaultCenter removeObserver:self]; [super viewWillDisappear:animated]; @@ -204,6 +205,39 @@ animated:animated]; } + +- (void) scrollToMessage:(LinphoneChatMessage *)message { + int index = [self indexOfMesssage:message]; + if (index < 0) + return; + + [self.tableView.layer removeAllAnimations]; + [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0] + atScrollPosition:UITableViewScrollPositionTop + animated:true]; +} + +-(int) indexOfMesssage:(LinphoneChatMessage *)message { + if (eventList.count == 0 || _chatRoom == nil) + return -1; + + int index = -1; + size_t count = eventList.count; + for (int i = (int)count - 1; i > 0; --i) { + LinphoneEventLog *event = [[eventList objectAtIndex:i] pointerValue]; + if (!(linphone_event_log_get_type(event) == LinphoneEventLogTypeConferenceChatMessage)) + continue;; + + LinphoneChatMessage *chat = linphone_event_log_get_chat_message(event); + if (chat == message) { + index = i; + break; + } + + } + return index; +} + #pragma mark - Property Functions - (void)setChatRoom:(LinphoneChatRoom *)room { @@ -275,6 +309,15 @@ static const int BASIC_EVENT_LIST=15; if (!_chatRoom && [[cell reuseIdentifier] isEqualToString:@"UIChatBubblePhotoCell"]) { [(UIChatBubbleTextCell *)cell clearEncryptedFiles]; } + if ([cell isKindOfClass:[UIChatBubbleTextCell class]] ||[cell isKindOfClass:[UIChatBubblePhotoCell class]]) + [(UIChatBubbleTextCell *)cell dismissPopup]; +} + +-(void) dismissMessagesPopups { + for (UITableViewCell *cell in self.tableView.visibleCells) { + if (![[cell reuseIdentifier] isEqualToString:NSStringFromClass(UIChatNotifiedEventCell.class)]) + [(UIChatBubbleTextCell *)cell dismissPopup]; + } } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { @@ -301,6 +344,8 @@ static const int BASIC_EVENT_LIST=15; [cell setChatRoomDelegate:_chatRoomDelegate]; [super accessoryForCell:cell atPath:indexPath]; cell.selectionStyle = UITableViewCellSelectionStyleNone; + cell.tableController = self; + cell.popupMenuAllowed = true; return cell; } else { kCellId = NSStringFromClass(UIChatNotifiedEventCell.class); diff --git a/Classes/ChatConversationView.h b/Classes/ChatConversationView.h index 06bb89c17..7c76cb286 100644 --- a/Classes/ChatConversationView.h +++ b/Classes/ChatConversationView.h @@ -32,7 +32,8 @@ #import "UIImageViewDeletable.h" #import "UIConfirmationDialog.h" #import "UIInterfaceStyleButton.h" - +#import "linphoneapp-Swift.h" +#import "UIChatReplyBubbleView.h" #include "linphone/linphonecore.h" @@ -49,7 +50,7 @@ @interface ChatConversationView : TPMultiLayoutViewController { + UIDocumentInteractionControllerDelegate, UISearchBarDelegate, UIImageViewDeletableDelegate,QLPreviewControllerDelegate, UICollectionViewDataSource,UICollectionViewDelegate,UIDocumentMenuDelegate,UIDocumentPickerDelegate,UITableViewDataSource, UITableViewDelegate> { OrderedDictionary *imageQualities; BOOL scrollOnGrowingEnabled; BOOL composingVisible; @@ -92,6 +93,36 @@ @property (weak, nonatomic) IBOutlet UIInterfaceStyleButton *toggleMenuButton; @property (weak, nonatomic) IBOutlet UIImageView *ephemeralndicator; + +// Voice recording +@property (strong, nonatomic) IBOutlet UIView *vrView; +@property (weak, nonatomic) IBOutlet UIView *vrInnerView; +@property (weak, nonatomic) IBOutlet UIButton *vrDeleteButton; +@property (weak, nonatomic) IBOutlet UIButton *vrPlayButton; +@property (weak, nonatomic) IBOutlet UIImageView *vrWave; +@property (weak, nonatomic) IBOutlet UIView *vrWaveMask; +@property (weak, nonatomic) IBOutlet UIView *vrWaveMaskPlayer; +@property (weak, nonatomic) IBOutlet UILabel *vrDurationLabel; +@property NSTimer *vrRecordTimer; +@property NSTimer *vrPlayerTimer; +@property (weak, nonatomic) IBOutlet UIButton *toggleRecord; +@property BOOL isVoiceRecording; +@property BOOL isPendingVoiceRecord; +@property BOOL isPlayingVoiceRecording; +@property LinphoneRecorder *voiceRecorder; +@property LinphonePlayer *sharedVoicePlayer; +@property BOOL showVoiceRecorderView; +@property BOOL preservePendingActions; + +// Reply +@property (weak, nonatomic) IBOutlet UIView *replyView; +@property BOOL showReplyView; +@property UIChatReplyBubbleView *replyBubble; + +// Forward +@property LinphoneChatMessage *pendingForwardMessage; + + + (void)markAsRead:(LinphoneChatRoom *)chatRoom; + (void)autoDownload:(LinphoneChatMessage *)message; +(NSString *)getKeyFromFileType:(NSString *)fileType fileName:(NSString *)name; @@ -123,4 +154,10 @@ - (NSURL *)getICloudFileUrl:(NSString *)name; - (void)removeCallBacks; +-(void) startSharedPlayer:(const char *)path; +-(void) stopSharedPlayer; +-(BOOL) sharedPlayedIsPlaying:(const char *)path; + +-(void) initiateReplyViewForMessage:(LinphoneChatMessage *)message; + @end diff --git a/Classes/ChatConversationView.m b/Classes/ChatConversationView.m index bfdca00f3..0a4517f9b 100644 --- a/Classes/ChatConversationView.m +++ b/Classes/ChatConversationView.m @@ -120,6 +120,7 @@ [NSNumber numberWithFloat:0.5], NSLocalizedString(@"Average", nil), [NSNumber numberWithFloat:0.0], NSLocalizedString(@"Minimum", nil), nil]; composingVisible = false; + [self initSharedPlayer]; } return self; } @@ -136,7 +137,7 @@ static UICompositeViewDescription *compositeDescription = nil; if (compositeDescription == nil) { compositeDescription = [[UICompositeViewDescription alloc] init:self.class statusBar:StatusBarView.class - tabBar:TabBarView.class + tabBar:nil sideMenu:SideMenuView.class fullscreen:false isLeftFragment:NO @@ -186,7 +187,14 @@ static UICompositeViewDescription *compositeDescription = nil; [_tableController setChatRoomDelegate:self]; [_imagesCollectionView registerClass:[UIImageViewDeletable class] forCellWithReuseIdentifier:NSStringFromClass([UIImageViewDeletable class])]; [_imagesCollectionView setDataSource:self]; + [_imagesCollectionView setDelegate:self]; [_toggleSelectionButton setImage:[UIImage imageNamed:@"select_all_default.png"] forState:UIControlStateSelected]; + + _vrInnerView.layer.cornerRadius = 5.0f; + _vrInnerView.layer.masksToBounds = YES; + _vrWaveMaskPlayer.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"color_L"]]; // rgba(1,88,7,0.2); + _showVoiceRecorderView = false; + } - (void)refreshData { @@ -202,6 +210,10 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(applicationWillEnterBackground) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification @@ -222,6 +234,16 @@ static UICompositeViewDescription *compositeDescription = nil; selector:@selector(onLinphoneCoreReady:) name:kLinphoneGlobalStateUpdate object:nil]; + + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(endVoicePlayingIfDoingSO:) + name:kLinphoneVoiceMessagePlayerLostFocus + object:nil]; + + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(endVoicePlayingIfDoingSO:) + name:kLinphoneVoiceMessagePlayerEOF + object:nil]; if ([_fileContext count] > 0) { [UIView animateWithDuration:0 delay:0 @@ -229,13 +251,14 @@ static UICompositeViewDescription *compositeDescription = nil; animations:^{ // resizing imagesView CGRect imagesFrame = [_imagesView frame]; - imagesFrame.origin.y = [_messageView frame].origin.y - 100; - imagesFrame.size.height = 100; + imagesFrame.origin.y = [_messageView frame].origin.y - 120; + imagesFrame.size.height = 120; [_imagesView setFrame:imagesFrame]; // resizing chatTable CGRect tableViewFrame = [_tableController.tableView frame]; - tableViewFrame.size.height -= 100; + tableViewFrame.size.height -= 120; [_tableController.tableView setFrame:tableViewFrame]; + [self updateFramesInclRecordingAndReplyView]; } completion:nil]; } @@ -245,10 +268,30 @@ static UICompositeViewDescription *compositeDescription = nil; CGRect popupFrame = _popupMenu.frame; popupFrame.size.height = 44 * [_popupMenu numberOfRowsInSection:0]; _popupMenu.frame = popupFrame; + + // Voice recording & Replies + _vrView.hidden = true; + _toggleRecord.enabled = linphone_core_get_calls_nb(LC) == 0; + _replyView.hidden = true; + _preservePendingActions = false; + + _toggleRecord.enabled = linphone_core_get_calls_nb(LC) == 0; } - (void)viewWillDisappear:(BOOL)animated { + + if (!_preservePendingActions) + [self cancelVoiceRecording]; + + if (!_preservePendingActions) + [self closePendingReply]; + + + + else if (_isVoiceRecording) + [self stopVoiceRecording]; + [super viewWillDisappear:animated]; [self removeCallBacks]; @@ -256,7 +299,9 @@ static UICompositeViewDescription *compositeDescription = nil; [_messageField resignFirstResponder]; [self setComposingVisible:false withDelay:0]; // will hide the "user is composing.." message - + + [self stopAllPlays]; + [NSNotificationCenter.defaultCenter removeObserver:self]; PhoneMainView.instance.currentRoom = NULL; } @@ -287,10 +332,24 @@ static UICompositeViewDescription *compositeDescription = nil; _backButton.hidden = _tableController.isEditing; [_tableController scrollToBottom:true]; [self refreshImageDrawer]; + [self stopAllPlays]; + } #pragma mark - +- (void)applicationWillEnterBackground{ + if (!_preservePendingActions) + [self cancelVoiceRecording]; + else if (_isVoiceRecording) + [self stopVoiceRecording]; + if (!_preservePendingActions) + [self closePendingReply]; + [self stopAllPlays]; + +} + + - (void)configureForRoom:(BOOL)editing { if (!_chatRoom) { _chatView.hidden = YES; @@ -347,6 +406,7 @@ static UICompositeViewDescription *compositeDescription = nil; [self setupPopupMenu]; _ephemeralndicator.hidden = !linphone_chat_room_ephemeral_enabled(_chatRoom); } + [self handlePendingTransferIfAny]; } @@ -374,7 +434,7 @@ static UICompositeViewDescription *compositeDescription = nil; } -(NSData *) nsDataRead { - NSString* groupName = [NSString stringWithFormat:@"group.%@.linphoneExtension",[[NSBundle mainBundle] bundleIdentifier]]; + NSString* groupName = @"group.com.clavys.frogtrust.store"; NSString *path =[[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupName] path]; NSString *fullCacheFilePathPath = [NSString stringWithFormat:@"%@/%@",path,@"nsData"]; return[NSData dataWithContentsOfFile:fullCacheFilePathPath]; @@ -382,7 +442,8 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)shareFile { - NSString* groupName = [NSString stringWithFormat:@"group.%@.linphoneExtension",[[NSBundle mainBundle] bundleIdentifier]]; + NSString* groupName = @"group.com.clavys.frogtrust.store"; + NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:groupName]; NSDictionary *dict = [defaults valueForKey:@"photoData"]; @@ -429,6 +490,9 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)callUpdateEvent:(NSNotification *)notif { [self updateSuperposedButtons]; + _toggleRecord.enabled = linphone_core_get_calls_nb(LC) == 0; + [_tableController.tableView reloadData]; + } - (void)update { @@ -456,17 +520,20 @@ static UICompositeViewDescription *compositeDescription = nil; } } -- (BOOL)sendMessage:(NSString *)message withExterlBodyUrl:(NSURL *)externalUrl { +- (BOOL)sendMessage:(NSString *)message withExterlBodyUrl:(NSURL *)externalUrl rootMessage:(LinphoneChatMessage *)rootMessage { if (_chatRoom == NULL) { LOGW(@"Cannot send message: No chatroom"); return FALSE; } - LinphoneChatMessage *msg = linphone_chat_room_create_message(_chatRoom, [message UTF8String]); + LinphoneChatMessage *msg = rootMessage; + if (message && message.length > 0) + linphone_chat_message_add_utf8_text_content(msg, message.UTF8String); + if (externalUrl) { linphone_chat_message_set_external_body_url(msg, [[externalUrl absoluteString] UTF8String]); } - + // we must ref & unref message because in case of error, it will be destroy otherwise linphone_chat_message_send(msg); @@ -509,15 +576,15 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)confirmShare:(NSData *)data url:(NSString *)url fileName:(NSString *)fileName { DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:@""]; dispatch_async(dispatch_get_main_queue(), ^{ - [sheet addButtonWithTitle:NSLocalizedString(@"Send to this friend", nil) + [sheet addButtonWithTitle:NSLocalizedString(@"send to this conversation", nil) block:^() { if (![[self.messageField text] isEqualToString:@""]) { - [self sendMessageInMessageField]; + [self sendMessageInMessageField:linphone_chat_room_create_empty_message(_chatRoom)]; } if (url) - [self sendMessage:url withExterlBodyUrl:nil]; + [self sendMessage:url withExterlBodyUrl:nil rootMessage:linphone_chat_room_create_empty_message(_chatRoom)]; else - [self startFileUpload:data withName:fileName]; + [self startFileUpload:data withName:fileName rootMessage:linphone_chat_room_create_empty_message(_chatRoom)]; }]; [sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil]; @@ -600,8 +667,8 @@ static UICompositeViewDescription *compositeDescription = nil; _addressLabel.frame = frame; } -- (void)sendMessageInMessageField { - if ([self sendMessage:[_messageField text] withExterlBodyUrl:nil]) { +- (void)sendMessageInMessageField:(LinphoneChatMessage *)rootMessage { + if ([self sendMessage:[_messageField text] withExterlBodyUrl:nil rootMessage:rootMessage]) { scrollOnGrowingEnabled = FALSE; [_messageField setText:@""]; scrollOnGrowingEnabled = TRUE; @@ -657,6 +724,7 @@ static UICompositeViewDescription *compositeDescription = nil; CGRect tableRect = [_tableController.view frame]; tableRect.size.height -= diff; [_tableController.view setFrame:tableRect]; + [self updateFramesInclRecordingAndReplyView]; // if we're showing the compose message, update it position if (![_composeLabel isHidden]) { @@ -681,28 +749,48 @@ static UICompositeViewDescription *compositeDescription = nil; } - (IBAction)onSendClick:(id)event { + + if (!linphone_core_is_network_reachable(LC)) { + //[PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView:@"send a message"] animated:YES completion:nil]; + //return; + } + + LinphoneChatMessage *rootMessage = _replyBubble ? linphone_chat_room_create_reply_message(_chatRoom, _replyBubble.message) : linphone_chat_room_create_empty_message(_chatRoom); + + if (_replyBubble) { + [self closePendingReply]; + } + + if (_isPendingVoiceRecord && _voiceRecorder && linphone_recorder_get_file(_voiceRecorder)) { + LinphoneContent * voiceContent = linphone_recorder_create_content(_voiceRecorder); + _isPendingVoiceRecord = false; + [self cancelVoiceRecording]; + [self stopVoiceRecordPlayer]; + linphone_chat_message_add_content(rootMessage, voiceContent); + } + if ([_fileContext count] > 0) { if (linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesConference) { - [self startMultiFilesUpload]; + [self startMultiFilesUpload:rootMessage]; } else { int i = 0; for (i = 0; i < [_fileContext count]-1; ++i) { - [self startUploadData:[_fileContext.datasArray objectAtIndex:i] withType:[_fileContext.typesArray objectAtIndex:i] withName:[_fileContext.namesArray objectAtIndex:i] andMessage:NULL]; + [self startUploadData:[_fileContext.datasArray objectAtIndex:i] withType:[_fileContext.typesArray objectAtIndex:i] withName:[_fileContext.namesArray objectAtIndex:i] andMessage:NULL rootMessage:rootMessage]; } if (isOneToOne) { - [self startUploadData:[_fileContext.datasArray objectAtIndex:i] withType:[_fileContext.typesArray objectAtIndex:i] withName:[_fileContext.namesArray objectAtIndex:i] andMessage:NULL]; + [self startUploadData:[_fileContext.datasArray objectAtIndex:i] withType:[_fileContext.typesArray objectAtIndex:i] withName:[_fileContext.namesArray objectAtIndex:i] andMessage:NULL rootMessage:rootMessage]; if (![[self.messageField text] isEqualToString:@""]) { - [self sendMessage:[_messageField text] withExterlBodyUrl:nil]; + [self sendMessage:[_messageField text] withExterlBodyUrl:nil rootMessage:rootMessage]; } } else { - [self startUploadData:[_fileContext.datasArray objectAtIndex:i] withType:[_fileContext.typesArray objectAtIndex:i] withName:[_fileContext.namesArray objectAtIndex:i] andMessage:[self.messageField text]]; + [self startUploadData:[_fileContext.datasArray objectAtIndex:i] withType:[_fileContext.typesArray objectAtIndex:i] withName:[_fileContext.namesArray objectAtIndex:i] andMessage:[self.messageField text] rootMessage:rootMessage]; } } [self clearMessageView]; return; } - [self sendMessageInMessageField]; + [self sendMessageInMessageField:rootMessage]; } - (IBAction)onListTap:(id)sender { @@ -759,14 +847,11 @@ static UICompositeViewDescription *compositeDescription = nil; } - (IBAction)onMessageChange:(id)sender { - if ([[_messageField text] length] > 0) { - [_sendButton setEnabled:TRUE]; - } else { - [_sendButton setEnabled:FALSE]; - } + [self setSendButtonState]; } - (IBAction)onPictureClick:(id)event { + _preservePendingActions = true; [_messageField resignFirstResponder]; [ImagePickerView SelectImageFromDevice:self atPosition:_pictureButton inView:self.view withDocumentMenuDelegate:self]; @@ -800,15 +885,15 @@ static UICompositeViewDescription *compositeDescription = nil; #pragma mark ChatRoomDelegate -- (BOOL)startMultiFilesUpload { +- (BOOL)startMultiFilesUpload:(LinphoneChatMessage *)rootMessage { FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init]; [fileTransfer setText:[self.messageField text]]; - [fileTransfer uploadFileContent:_fileContext forChatRoom:_chatRoom]; + [fileTransfer uploadFileContent:_fileContext forChatRoom:_chatRoom rootMessage:rootMessage]; [_tableController scrollToBottom:true]; return TRUE; } -- (BOOL)startUploadData:(NSData *)data withType:(NSString*)type withName:(NSString *)name andMessage:(NSString *)message { +- (BOOL)startUploadData:(NSData *)data withType:(NSString*)type withName:(NSString *)name andMessage:(NSString *)message rootMessage:(LinphoneChatMessage *)rootMessage { FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init]; if (message) [fileTransfer setText:message]; @@ -818,38 +903,38 @@ static UICompositeViewDescription *compositeDescription = nil; } else if ([type isEqualToString:@"image"]) { key = @"localimage"; } - [fileTransfer uploadData:data forChatRoom:_chatRoom type:type subtype:type name:name key:key]; + [fileTransfer uploadData:data forChatRoom:_chatRoom type:type subtype:type name:name key:key rootMessage:rootMessage]; [_tableController scrollToBottom:true]; return TRUE; } -- (BOOL)startFileUpload:(NSData *)data withName:(NSString *)name { +- (BOOL)startFileUpload:(NSData *)data withName:(NSString *)name rootMessage:(LinphoneChatMessage *)rootMessage { FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init]; - [fileTransfer uploadFile:data forChatRoom:_chatRoom withName:name]; + [fileTransfer uploadFile:data forChatRoom:_chatRoom withName:name rootMessage:rootMessage]; [_tableController scrollToBottom:true]; return TRUE; } -- (BOOL)resendMultiFiles:(FileContext *)newFileContext message:(NSString *)message { +- (BOOL)resendMultiFiles:(FileContext *)newFileContext message:(NSString *)message rootMessage:(LinphoneChatMessage *)rootMessage { FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init]; if (message) [fileTransfer setText:message]; - [fileTransfer uploadFileContent:newFileContext forChatRoom:_chatRoom]; + [fileTransfer uploadFileContent:newFileContext forChatRoom:_chatRoom rootMessage:rootMessage]; [_tableController scrollToBottom:true]; return TRUE; } -- (BOOL)resendFile: (NSData *)data withName:(NSString *)name type:(NSString *)type key:(NSString *)key message:(NSString *)message { +- (BOOL)resendFile: (NSData *)data withName:(NSString *)name type:(NSString *)type key:(NSString *)key message:(NSString *)message rootMessage:(LinphoneChatMessage *)rootMessage{ FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init]; if (message) [fileTransfer setText:message]; - [fileTransfer uploadData:data forChatRoom:_chatRoom type:type subtype:type name:name key:key]; + [fileTransfer uploadData:data forChatRoom:_chatRoom type:type subtype:type name:name key:key rootMessage:rootMessage]; [_tableController scrollToBottom:true]; return TRUE; } -- (void)resendChat:(NSString *)message withExternalUrl:(NSString *)url { - [self sendMessage:message withExterlBodyUrl:[NSURL URLWithString:url]]; +- (void)resendChat:(NSString *)message withExternalUrl:(NSString *)url rootMessage:(LinphoneChatMessage *)rootMessage { + [self sendMessage:message withExterlBodyUrl:[NSURL URLWithString:url] rootMessage:rootMessage]; } #pragma mark ImagePickerDelegate @@ -1077,6 +1162,7 @@ static UICompositeViewDescription *compositeDescription = nil; [_messageView frame].origin.y - tableFrame.origin.y - composeIndicatorCompensation; [_tableController.view setFrame:tableFrame]; + // Scroll to bottom NSInteger lastSection = [_tableController.tableView numberOfSections] - 1; if (lastSection >= 0) { @@ -1102,9 +1188,14 @@ static UICompositeViewDescription *compositeDescription = nil; tableViewFrame.size.height = imagesFrame.origin.y - tableViewFrame.origin.y; [_tableController.tableView setFrame:tableViewFrame]; } + if (_showVoiceRecorderView) + _vrView.hidden = true; // force recalculate + if (_showReplyView) + _replyView.hidden = true; // force recalculate + [self updateFramesInclRecordingAndReplyView]; + } completion:^(BOOL finished){ - }]; } @@ -1158,6 +1249,7 @@ static UICompositeViewDescription *compositeDescription = nil; tableFrame.size.height = [_messageView frame].origin.y - tableFrame.origin.y - composeIndicatorCompensation; [_tableController.view setFrame:tableFrame]; + } if ([_fileContext count] > 0){ @@ -1170,6 +1262,7 @@ static UICompositeViewDescription *compositeDescription = nil; CGRect tableViewFrame = [_tableController.tableView frame]; tableViewFrame.size.height = imagesFrame.origin.y - tableViewFrame.origin.y; [_tableController.tableView setFrame:tableViewFrame]; + } // Scroll @@ -1183,10 +1276,15 @@ static UICompositeViewDescription *compositeDescription = nil; animated:FALSE]; } } + if (_showVoiceRecorderView) + _vrView.hidden = true; // force recalculate + if (_showReplyView) + _replyView.hidden = true; // force recalculate + [self updateFramesInclRecordingAndReplyView]; + } completion:^(BOOL finished){ - }]; } @@ -1381,26 +1479,31 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog return [_fileContext count]; } + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { + return UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) ? CGSizeMake(60, 60) : CGSizeMake(120, 120); +} + - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { UIImageViewDeletable *imgView = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([UIImageViewDeletable class]) forIndexPath:indexPath]; CGRect imgFrame = imgView.frame; imgFrame.origin.y = 5; if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) { - imgFrame.size.height = 50; + imgFrame.size.height = 60; } else { - imgFrame.size.height = 100; + imgFrame.size.height = 120; } [imgView.image setImage:[UIImage resizeImage:[_fileContext.previewsArray objectAtIndex:[indexPath item]] withMaxWidth:imgFrame.size.width andMaxHeight:imgFrame.size.height]]; [imgView setUuid:[_fileContext.uuidsArray objectAtIndex:[indexPath item]]]; [imgView setDeleteDelegate:self]; [imgView setFrame:imgFrame]; - [_sendButton setEnabled:TRUE]; + [self setSendButtonState]; return imgView; } - (void)refreshImageDrawer { - int heightDiff = UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) ? 55 : 105; + int heightDiff = UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) ? 65 : 125; if ([_fileContext count] == 0) { [UIView animateWithDuration:0 @@ -1416,10 +1519,10 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog CGRect tableViewFrame = [_tableController.tableView frame]; tableViewFrame.size.height = imagesFrame.origin.y - tableViewFrame.origin.y; [_tableController.tableView setFrame:tableViewFrame]; + [self updateFramesInclRecordingAndReplyView]; } completion:nil]; - if ([_messageField.text isEqualToString:@""]) - [_sendButton setEnabled:FALSE]; + [self setSendButtonState]; } else { // resizing imagesView CGRect imagesFrame = [_imagesView frame]; @@ -1430,6 +1533,7 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog CGRect tableViewFrame = [_tableController.tableView frame]; tableViewFrame.size.height = imagesFrame.origin.y - tableViewFrame.origin.y; [_tableController.tableView setFrame:tableViewFrame]; + [self updateFramesInclRecordingAndReplyView]; [_imagesCollectionView reloadData]; } } @@ -1488,7 +1592,7 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog NSFileCoordinator *co =[[NSFileCoordinator alloc] init]; NSError *error = nil; [co coordinateReadingItemAtURL:url options:0 error:&error byAccessor:^(NSURL * _Nonnull newURL) { - UIImage *image = [ChatConversationView drawText:[newURL lastPathComponent] image:[ChatConversationView getBasicImage] textSize:10]; + UIImage *image = [UIChatBubbleTextCell getImageFromFileName:[newURL lastPathComponent]]; [_fileContext addObject:[NSData dataWithContentsOfURL:newURL] name:[newURL lastPathComponent] type:@"file" image:image]; [self refreshImageDrawer]; }]; @@ -1592,4 +1696,322 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog } +// Voice redcording + + +- (IBAction)onVrDelete:(id)sender { + [self cancelVoiceRecording]; + [self stopVoiceRecordPlayer]; +} + +- (IBAction)onvrPlayPauseStop:(id)sender { + if (_isVoiceRecording) { + [self stopVoiceRecording]; + } else { + if (_isPlayingVoiceRecording) + [self stopVoiceRecordPlayer]; + else + [self playRecordedMessage]; + } +} + +- (IBAction)onVrStart:(id)sender { + if (_isVoiceRecording) { + [self stopVoiceRecording]; + } else { + [self startVoiceRecording]; + } +} + +-(void) createVoiceRecorder { + LinphoneRecorderParams *p = linphone_core_create_recorder_params(LC); + linphone_recorder_params_set_file_format(p, LinphoneRecorderFileFormatWav); + _voiceRecorder = linphone_core_create_recorder(LC, p); +} + +-(void) cancelVoiceRecording { + _showVoiceRecorderView = false; + _toggleRecord.selected = false; + [self updateFramesInclRecordingAndReplyView]; + _isPendingVoiceRecord = false; + _isVoiceRecording = false; + if (_voiceRecorder && linphone_recorder_get_state(_voiceRecorder) != LinphoneRecorderClosed) { + linphone_recorder_close(_voiceRecorder); + const char *recordingFile = linphone_recorder_get_file(_voiceRecorder); + if (recordingFile) { + [AppManager removeFileWithFile:[NSString stringWithUTF8String:recordingFile]]; + } + } + [self setSendButtonState]; +} + +-(void) stopVoiceRecording { + if (_voiceRecorder && linphone_recorder_get_state(_voiceRecorder) == LinphoneRecorderRunning) { + LOGI(@"[Chat Message Sending] Pausing / closing voice recorder"); + linphone_recorder_pause(_voiceRecorder); + linphone_recorder_close(_voiceRecorder); + _vrDurationLabel.text = [self formattedDuration:linphone_recorder_get_duration(_voiceRecorder)]; + } + _isVoiceRecording = false; + if ([LinphoneManager.instance lpConfigBoolForKey:@"voice_recording_send_right_away" withDefault:false]) { + [self onSendClick:nil]; + } + [_vrPlayButton setImage:[UIImage imageNamed:@"vr_play"] forState:UIControlStateNormal]; + _toggleRecord.selected = false; + _vrWaveMask.frame = CGRectZero; + [_vrRecordTimer invalidate]; + _isPendingVoiceRecord = linphone_recorder_get_duration(_voiceRecorder) > 0; + [self setSendButtonState]; + +} + +-(void) startVoiceRecording { + + if (!_voiceRecorder) + [self createVoiceRecorder]; + [CallManager.instance activateAudioSession]; + _toggleRecord.selected = true; + [_vrPlayButton setImage:[UIImage imageNamed:@"vr_stop"] forState:UIControlStateNormal]; + + + _showVoiceRecorderView = true; + [self updateFramesInclRecordingAndReplyView]; + _isVoiceRecording = true; + _vrWaveMaskPlayer.frame = CGRectZero; + + switch (linphone_recorder_get_state(_voiceRecorder)) { + case LinphoneRecorderClosed: { + NSString *filename = [NSString stringWithFormat:@"%@/voice-recording-%@.wav",[LinphoneManager imagesDirectory], [NSUUID UUID].UUIDString]; + linphone_recorder_open(_voiceRecorder, filename.UTF8String); + linphone_recorder_start(_voiceRecorder); + LOGW(@"[Chat Message Sending] Recorder is closed opening it with %@",filename); + break; + }; + case LinphoneRecorderRunning: { + LOGW(@"[Chat Message Sending] Recorder is already recording"); + break; + } + case LinphoneRecorderPaused: { + LOGW(@"[Chat Message Sending] Recorder isn't closed, resuming recording"); + linphone_recorder_start(_voiceRecorder); + } + } + _vrWaveMask.frame = _vrWave.frame; + _vrDurationLabel.text = [self formattedDuration:linphone_recorder_get_duration(_voiceRecorder)]; + _vrRecordTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 + target:self + selector:@selector(voiceRecordTimerUpdate) + userInfo:nil + repeats:YES]; + + +} + +-(void) voiceRecordTimerUpdate { + int recorderDuration = linphone_recorder_get_duration(_voiceRecorder); + if (recorderDuration > [LinphoneManager.instance lpConfigIntForKey:@"voice_recording_max_duration" withDefault:60000]) { + LOGW(@"[Chat Message Sending] Max duration for voice recording exceeded, stopping. (max = %d)",[LinphoneManager.instance lpConfigIntForKey:@"voice_recording_max_duration" withDefault:60000]); + [self stopVoiceRecording]; + } else { + _vrDurationLabel.text = [self formattedDuration:linphone_recorder_get_duration(_voiceRecorder)]; + CGRect r = _vrWaveMask.frame; + r.origin.x += 30; + r.size.width -= 30; + if (r.origin.x > _vrWave.frame.size.width) { + r = _vrWave.frame; + _vrWaveMask.frame = r; + } else { + [UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{ + _vrWaveMask.frame = r; + }completion:^(BOOL finished) {}]; + } + } +} + +// Playback Shared Player (new recording & chat bubble) + +- (void) initSharedPlayer { + LOGI(@"[Voice Message] Creating shared player"); + _sharedVoicePlayer = linphone_core_create_local_player(LC, [CallManager.instance getSpeakerSoundCard].UTF8String, nil, nil); + LinphonePlayerCbs *cbs = linphone_factory_create_player_cbs(linphone_factory_get()); + linphone_player_cbs_set_eof_reached(cbs, on_shared_player_eof_reached); + linphone_player_cbs_set_user_data(cbs, (__bridge void*)self); + linphone_player_add_callbacks(_sharedVoicePlayer, cbs); +} + +-(void) startSharedPlayer:(const char *)path { + LOGI(@"[Voice Message] Starting shared player path = %s",path); + if (linphone_player_get_user_data(_sharedVoicePlayer)) { + LOGI(@"[Voice Message] a play was requested (%s), but there is already one going (%s)",path,(const char *)linphone_player_get_user_data(_sharedVoicePlayer) ); + NSDictionary* userInfo = @{@"path": [NSString stringWithUTF8String:linphone_player_get_user_data(_sharedVoicePlayer)]}; + [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneVoiceMessagePlayerLostFocus object:nil userInfo:userInfo]; + } + [CallManager.instance changeRouteToSpeaker]; + linphone_player_set_user_data(_sharedVoicePlayer, (void *)path); + linphone_player_open(_sharedVoicePlayer, path); + linphone_player_start(_sharedVoicePlayer); +} + +-(void) stopSharedPlayer { + LOGI(@"[Voice Message] Stopping shared player path = %s",linphone_player_get_user_data(_sharedVoicePlayer) ? (const char *)linphone_player_get_user_data(_sharedVoicePlayer) : "nil"); + linphone_player_pause(_sharedVoicePlayer); + linphone_player_seek(_sharedVoicePlayer,0); + linphone_player_close(_sharedVoicePlayer); + linphone_player_set_user_data(_sharedVoicePlayer, nil); +} + +-(BOOL) sharedPlayedIsPlaying:(const char *)path { + return path && linphone_player_get_user_data(_sharedVoicePlayer) && !strcmp(path,linphone_player_get_user_data(_sharedVoicePlayer)); +} + +void on_shared_player_eof_reached(LinphonePlayer *p) { + LOGI(@"[Voice Message] End of file reached for player"); + const char * currentPlayedFile = (const char *) linphone_player_get_user_data(p); + if (currentPlayedFile) { + NSDictionary* userInfo = @{@"path": [NSString stringWithUTF8String:currentPlayedFile]}; + [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneVoiceMessagePlayerEOF object:nil userInfo:userInfo]; + } +} + +// Playback of new recordings + +-(void) playRecordedMessage { + [_vrPlayButton setImage:[UIImage imageNamed:@"vr_stop"] forState:UIControlStateNormal]; + _vrDurationLabel.text = [self formattedDuration:linphone_player_get_duration(_sharedVoicePlayer)]; + _vrWaveMask.frame = CGRectZero; + CGRect r = CGRectZero; + r.size.height = _vrInnerView.frame.size.height; + _vrWaveMaskPlayer.frame = r; + _vrPlayerTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 + target:self + selector:@selector(voicePlayTimerUpdate) + userInfo:nil + repeats:YES]; + [self startSharedPlayer:linphone_recorder_get_file(_voiceRecorder)]; + [self animPlayerOnce]; + _isPlayingVoiceRecording = true; +} + +-(void) voicePlayTimerUpdate { + _vrDurationLabel.text = [self formattedDuration:linphone_player_get_duration(_sharedVoicePlayer)]; + [self animPlayerOnce]; +} + +-(void) animPlayerOnce { + CGRect r = _vrWaveMaskPlayer.frame; + r.size.width += _vrInnerView.frame.size.width / ((linphone_player_get_duration(_sharedVoicePlayer) / 1000)+1) ; + if (r.size.width > _vrInnerView.frame.size.width) { + r.size.width = _vrInnerView.frame.size.width; + } + [UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{ + _vrWaveMaskPlayer.frame = r; + }completion:^(BOOL finished) {}]; +} + +-(void) endVoicePlayingIfDoingSO:(NSNotification *)notif { + if (_isPlayingVoiceRecording) + [self stopVoiceRecordPlayer]; +} + +-(void) stopVoiceRecordPlayer { + [self stopSharedPlayer]; + [_vrPlayButton setImage:[UIImage imageNamed:@"vr_play"] forState:UIControlStateNormal]; + _isPlayingVoiceRecording = false; + [_vrPlayerTimer invalidate]; + _vrWaveMaskPlayer.frame = CGRectZero; +} + +-(NSString *)formattedDuration:(long)valueMs { + return [NSString stringWithFormat:@"%02ld:%02ld", valueMs/ 60000, (valueMs % 60000) / 1000 ]; +} + +-(void) updateFramesInclRecordingAndReplyView { // place below the messages table. + BOOL showHideVoice = _showVoiceRecorderView != !_vrView.hidden; + if (showHideVoice) + _vrView.hidden = !_showVoiceRecorderView; + + CGRect vrFrame = _vrView.frame; + CGRect tableFrame = _tableController.tableView.frame; + if (showHideVoice) { + tableFrame.size.height = _showVoiceRecorderView ? tableFrame.size.height - vrFrame.size.height : tableFrame.size.height + vrFrame.size.height; + _tableController.tableView.frame = tableFrame; + [_tableController.tableView reloadData]; + } + vrFrame.origin.y = tableFrame.origin.y+tableFrame.size.height; + _vrView.frame = vrFrame; + + BOOL showHideReply = _showReplyView != !_replyView.hidden; + if (showHideReply) + _replyView.hidden = !_showReplyView; + + CGRect repFrame = _replyView.frame; + tableFrame = _tableController.tableView.frame; + if (showHideReply) { + tableFrame.size.height = _showReplyView ? tableFrame.size.height - repFrame.size.height : tableFrame.size.height + repFrame.size.height; + _tableController.tableView.frame = tableFrame; + [_tableController.tableView reloadData]; + } + repFrame.origin.y = _showVoiceRecorderView ? vrFrame.origin.y + vrFrame.size.height : tableFrame.origin.y+tableFrame.size.height; + _replyView.frame = repFrame; + +} + +-(void) stopAllPlays { + if (linphone_player_get_user_data(_sharedVoicePlayer)) { + NSDictionary* userInfo = @{@"path": [NSString stringWithUTF8String:linphone_player_get_user_data(_sharedVoicePlayer)]}; + [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneVoiceMessagePlayerLostFocus object:nil userInfo:userInfo]; + } +} + +// send button state + +-(void) setSendButtonState { + _sendButton.enabled = !_isVoiceRecording && ((_isPendingVoiceRecord && linphone_recorder_get_duration(_voiceRecorder) > 0) || [[_messageField text] length] > 0 || _fileContext.count > 0); +} + + +// Reply + +-(void) closePendingReply { + if (_replyBubble != nil) { + _showReplyView = false; + [_replyBubble.view removeFromSuperview]; + [self updateFramesInclRecordingAndReplyView]; + _replyBubble = nil; + } +} + +-(void) initiateReplyViewForMessage:(LinphoneChatMessage *)message { + if (_replyBubble != nil) + [self closePendingReply]; + _replyBubble = [[UIChatReplyBubbleView alloc] initWithNibName:@"UIChatReplyBubbleView" bundle:nil]; + [self addChildViewController:_replyBubble]; + [_replyView addSubview:_replyBubble.view]; + [_replyBubble didMoveToParentViewController:self]; + [_replyBubble configureForMessage:message withDimissBlock:^{ + [self closePendingReply]; + } hideDismiss:false withClickBlock:^{}]; + _showReplyView = true; + [self updateFramesInclRecordingAndReplyView]; + [self.tableController scrollToMessage:message]; +} + +-(void) handlePendingTransferIfAny { + if (self.pendingForwardMessage) { + LinphoneChatMessage *message = self.pendingForwardMessage; + self.pendingForwardMessage = nil; + UIConfirmationDialog *d = [UIConfirmationDialog ShowWithMessage:NSLocalizedString(@"Transfer this message to this conversation ?",nil) + cancelMessage:nil + confirmMessage:NSLocalizedString(@"TRANSFER",nil) + onCancelClick:^() {} + onConfirmationClick:^() { + linphone_chat_message_send(linphone_chat_room_create_forward_message(_chatRoom, message)); + + }]; + d.forwardImage.hidden = NO; + [d setSpecialColor]; + } +} + + @end diff --git a/Classes/ChatsListTableView.h b/Classes/ChatsListTableView.h index 30a054770..3c0593ebc 100644 --- a/Classes/ChatsListTableView.h +++ b/Classes/ChatsListTableView.h @@ -28,6 +28,7 @@ @property (weak, nonatomic) IBOutlet UIView *waitView; @property bctbx_list_t *data; + - (void)loadData; - (void)markCellAsRead:(LinphoneChatRoom *)chatRoom; @end diff --git a/Classes/ChatsListTableView.m b/Classes/ChatsListTableView.m index ae6ad0196..935fadcd7 100644 --- a/Classes/ChatsListTableView.m +++ b/Classes/ChatsListTableView.m @@ -160,6 +160,13 @@ static int sorted_history_comparison(LinphoneChatRoom *to_insert, LinphoneChatRo [cell setChatRoom:(LinphoneChatRoom *)bctbx_list_nth_data(_data, (int)[indexPath row])]; [super accessoryForCell:cell atPath:indexPath]; + BOOL forwardMode = VIEW(ChatConversationView).pendingForwardMessage != nil; + cell.forwardIcon.hidden = !forwardMode; + if (forwardMode) { + cell.ephemeral.hidden = true; + cell.imdmIcon.hidden = true; + } + return cell; } diff --git a/Classes/ChatsListView.h b/Classes/ChatsListView.h index 8525e4aac..c0046957d 100644 --- a/Classes/ChatsListView.h +++ b/Classes/ChatsListView.h @@ -34,6 +34,9 @@ @property(weak, nonatomic) IBOutlet UIBackToCallButton *backToCallButton; @property (weak, nonatomic) IBOutlet UIView *waitView; @property (weak, nonatomic) IBOutlet UIInterfaceStyleButton *toggleSelectionButton; +@property (weak, nonatomic) IBOutlet UILabel *forwardTitle; +@property (weak, nonatomic) IBOutlet UIInterfaceStyleButton *cancelForwardButton; + - (IBAction)onAddGroupChatClick:(id)event; - (IBAction)onAddClick:(id)event; diff --git a/Classes/ChatsListView.m b/Classes/ChatsListView.m index a3acc5b7c..e80421b5f 100644 --- a/Classes/ChatsListView.m +++ b/Classes/ChatsListView.m @@ -53,8 +53,28 @@ [button addTarget:self action:@selector(crashButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:button];*/ + + BOOL forwardMode = VIEW(ChatConversationView).pendingForwardMessage != nil; + + _tableController.editButton.hidden = forwardMode; + _forwardTitle.text = NSLocalizedString(@"Select a discussion or create a new one",nil); + _forwardTitle.hidden = !forwardMode; + _cancelForwardButton.hidden = !forwardMode; + + _tableController.tableView.frame = CGRectMake(0, 66 + (forwardMode ? _forwardTitle.frame.size.height : 0), _tableController.tableView.frame.size.width, self.view.frame.size.height - 66 - ( forwardMode ? _forwardTitle.frame.size.height : 0 )); + _addButton.frame = CGRectMake(forwardMode ? 82 : 0 , _addButton.frame.origin.y, _addButton.frame.size.width, _addButton.frame.size.height); + _addGroupChatButton.frame = CGRectMake(forwardMode ? 164 : 82 , _addGroupChatButton.frame.origin.y, _addGroupChatButton.frame.size.width, _addGroupChatButton.frame.size.height); + } + + +- (void)ephemeralDeleted:(NSNotification *)notif { + //LinphoneChatRoom *r =[[notif.userInfo objectForKey:@"room"] intValue]; + [self.tableController loadData]; +} + + - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; @@ -150,8 +170,9 @@ static UICompositeViewDescription *compositeDescription = nil; assert(NO); } -- (void)ephemeralDeleted:(NSNotification *)notif { - [self.tableController loadData]; +- (IBAction)onCancelForwardClicked:(id)sender { + VIEW(ChatConversationView).pendingForwardMessage = nil; + [PhoneMainView.instance popCurrentView]; } @end diff --git a/Classes/FirstLoginView.m b/Classes/FirstLoginView.m index f00b1182d..a0d10d0a6 100644 --- a/Classes/FirstLoginView.m +++ b/Classes/FirstLoginView.m @@ -200,7 +200,7 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)onLoginClick:(id)sender { if (!linphone_core_is_network_reachable(LC)) { - [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView] animated:YES completion:nil]; + [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView:@"configure an account"] animated:YES completion:nil]; return; } diff --git a/Classes/KIF b/Classes/KIF deleted file mode 160000 index 99ae001ae..000000000 --- a/Classes/KIF +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 99ae001ae51f4883d189e3f77544828ec035395d diff --git a/Classes/LinphoneManager.h b/Classes/LinphoneManager.h index 1054595b7..235caee7c 100644 --- a/Classes/LinphoneManager.h +++ b/Classes/LinphoneManager.h @@ -60,6 +60,8 @@ extern NSString *const kLinphoneFileTransferRecvUpdate; extern NSString *const kLinphoneQRCodeFound; extern NSString *const kLinphoneChatCreateViewChange; extern NSString *const kLinphoneEphemeralMessageDeletedInRoom; +extern NSString *const kLinphoneVoiceMessagePlayerEOF; +extern NSString *const kLinphoneVoiceMessagePlayerLostFocus; extern NSString *const kLinphoneConfStateParticipantListChanged; extern NSString *const kLinphoneConfStateChanged; diff --git a/Classes/LinphoneManager.m b/Classes/LinphoneManager.m index eaa693fca..3de5afe82 100644 --- a/Classes/LinphoneManager.m +++ b/Classes/LinphoneManager.m @@ -75,6 +75,8 @@ NSString *const kLinphoneFileTransferRecvUpdate = @"LinphoneFileTransferRecvUpda NSString *const kLinphoneQRCodeFound = @"LinphoneQRCodeFound"; NSString *const kLinphoneChatCreateViewChange = @"LinphoneChatCreateViewChange"; NSString *const kLinphoneEphemeralMessageDeletedInRoom = @"LinphoneEphemeralMessageDeletedInRoom"; +NSString *const kLinphoneVoiceMessagePlayerEOF = @"LinphoneVoiceMessagePlayerEOF"; +NSString *const kLinphoneVoiceMessagePlayerLostFocus = @"LinphoneVoiceMessagePlayerLostFocus"; NSString *const kLinphoneConfStateChanged = @"kLinphoneConfStateChanged"; NSString *const kLinphoneConfStateParticipantListChanged = @"kLinphoneConfStateParticipantListChanged"; @@ -1801,6 +1803,7 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) { return; if (linphone_core_local_permission_enabled(LC)) return; + if (![defaults boolForKey: alertSuppressionKey]) { UIAlertController *noticeView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Local network usage", nil) @@ -1825,7 +1828,7 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) { - (void)call:(const LinphoneAddress *)iaddr { // First verify that network is available, abort otherwise. if (!linphone_core_is_network_reachable(theLinphoneCore)) { - [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView] animated:YES completion:nil]; + [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView:@"place a call"] animated:YES completion:nil]; return; } @@ -2270,10 +2273,7 @@ void conference_device_changed(LinphoneConference *conference, const LinphonePar void linphone_iphone_conference_state_changed(LinphoneCore *lc, LinphoneConference *conf,LinphoneConferenceState state) { if (state == LinphoneConferenceStateCreated) { - LinphoneConferenceCbs * cbs = linphone_conference_get_current_callbacks(conf); - if (!cbs) { - cbs = linphone_factory_create_conference_cbs(linphone_factory_get()); - } + LinphoneConferenceCbs * cbs = linphone_factory_create_conference_cbs(linphone_factory_get()); linphone_conference_cbs_set_participant_added(cbs, conference_participant_changed); linphone_conference_cbs_set_participant_device_added(cbs, conference_device_changed); linphone_conference_cbs_set_participant_device_removed(cbs, conference_device_changed); diff --git a/Classes/LinphoneUI/Base.lproj/UIChatBubblePhotoCell.xib b/Classes/LinphoneUI/Base.lproj/UIChatBubblePhotoCell.xib index d320d6580..8cae658dc 100644 --- a/Classes/LinphoneUI/Base.lproj/UIChatBubblePhotoCell.xib +++ b/Classes/LinphoneUI/Base.lproj/UIChatBubblePhotoCell.xib @@ -29,15 +29,21 @@ + - - + + + + + + + - + @@ -57,134 +63,182 @@ - + + + + + + - + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - @@ -192,21 +246,11 @@ - - - - - - - - - - @@ -217,5 +261,8 @@ + + + diff --git a/Classes/LinphoneUI/Base.lproj/UIChatBubbleTextCell.xib b/Classes/LinphoneUI/Base.lproj/UIChatBubbleTextCell.xib index 481e4541a..d19e81d0c 100644 --- a/Classes/LinphoneUI/Base.lproj/UIChatBubbleTextCell.xib +++ b/Classes/LinphoneUI/Base.lproj/UIChatBubbleTextCell.xib @@ -18,6 +18,8 @@ + + @@ -41,6 +43,17 @@ + + + + + @@ -80,5 +93,6 @@ + diff --git a/Classes/LinphoneUI/Base.lproj/UIChatCell.xib b/Classes/LinphoneUI/Base.lproj/UIChatCell.xib index 67257c9d5..9ed041472 100644 --- a/Classes/LinphoneUI/Base.lproj/UIChatCell.xib +++ b/Classes/LinphoneUI/Base.lproj/UIChatCell.xib @@ -14,6 +14,7 @@ + @@ -64,10 +65,6 @@ - + + @@ -101,6 +106,7 @@ + diff --git a/Classes/LinphoneUI/Base.lproj/UIChatReplyBubbleView.xib b/Classes/LinphoneUI/Base.lproj/UIChatReplyBubbleView.xib new file mode 100644 index 000000000..4a657bdc5 --- /dev/null +++ b/Classes/LinphoneUI/Base.lproj/UIChatReplyBubbleView.xib @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Classes/LinphoneUI/Base.lproj/UIConfirmationDialog.xib b/Classes/LinphoneUI/Base.lproj/UIConfirmationDialog.xib index 3bc5b8c8e..eb265f061 100644 --- a/Classes/LinphoneUI/Base.lproj/UIConfirmationDialog.xib +++ b/Classes/LinphoneUI/Base.lproj/UIConfirmationDialog.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -15,6 +13,7 @@ + @@ -25,26 +24,26 @@ - + - + - - + - - + + - + + diff --git a/Classes/LinphoneUI/UIChatBubblePhotoCell.h b/Classes/LinphoneUI/UIChatBubblePhotoCell.h index 43b6b6eed..49555feaa 100644 --- a/Classes/LinphoneUI/UIChatBubblePhotoCell.h +++ b/Classes/LinphoneUI/UIChatBubblePhotoCell.h @@ -41,9 +41,18 @@ @property(strong, nonatomic) IBOutlet UITapGestureRecognizer *imageGestureRecognizer; @property (weak, nonatomic) IBOutlet UIButton *fileButton; @property (weak, nonatomic) IBOutlet UIView *fileView; -@property (strong, nonatomic) IBOutlet UILongPressGestureRecognizer *plusLongGestureRecognizer; @property(strong, nonatomic) NSMutableArray *contentViews; +// Video recordings +@property (weak, nonatomic) IBOutlet UIView *vrView; +@property (weak, nonatomic) IBOutlet UIButton *vrPlayPause; +@property (weak, nonatomic) IBOutlet UILabel *vrTimerLabel; +@property (weak, nonatomic) IBOutlet UIImageView *vrWave; +@property (weak, nonatomic) IBOutlet UIView *vrWaveMaskPlayback; +@property NSTimer *vrPlayerTimer; +@property NSString *voiceRecordingFile; + + - (void)setEvent:(LinphoneEventLog *)event; - (void)setChatMessage:(LinphoneChatMessage *)message; @@ -51,10 +60,8 @@ - (IBAction)onDownloadClick:(id)event; - (IBAction)onImageClick:(id)event; - (IBAction)onCancelClick:(id)sender; -- (IBAction)onResendClick:(id)event; - (IBAction)onPlayClick:(id)sender; - (IBAction)onFileClick:(id)sender; -- (IBAction)onPlusClick:(id)sender; @end diff --git a/Classes/LinphoneUI/UIChatBubblePhotoCell.m b/Classes/LinphoneUI/UIChatBubblePhotoCell.m index d96ae3cb2..f94ea00d7 100644 --- a/Classes/LinphoneUI/UIChatBubblePhotoCell.m +++ b/Classes/LinphoneUI/UIChatBubblePhotoCell.m @@ -26,6 +26,13 @@ #import #import +#define voicePlayer VIEW(ChatConversationView).sharedVoicePlayer +#define chatView VIEW(ChatConversationView) +#define FILE_ICON_TAG 0 +#define REALIMAGE_TAG 1 + + + @implementation UIChatBubblePhotoCell { FileTransferDelegate *_ftd; CGSize imageSize, bubbleSize, videoDefaultSize; @@ -54,6 +61,13 @@ assetIsLoaded = FALSE; self.contentView.userInteractionEnabled = NO; _contentViews = [[NSMutableArray alloc] init]; + + + self.vrView.layer.cornerRadius = 30.0f; + self.vrView.layer.masksToBounds = YES; + [self.innerView addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onPopupMenuPressed)]]; + self.messageText.userInteractionEnabled = false; + } return self; } @@ -73,7 +87,6 @@ - (void)setChatMessage:(LinphoneChatMessage *)amessage { _imageGestureRecognizer.enabled = NO; - _plusLongGestureRecognizer.enabled = NO; _messageImageView.image = nil; _finalImage.image = nil; _finalImage.hidden = TRUE; @@ -98,6 +111,7 @@ } - (void) loadImageAsset:(PHAsset*) asset image:(UIImage *)image { + _finalImage.tag = REALIMAGE_TAG; dispatch_async(dispatch_get_main_queue(), ^{ [_finalImage setImage:image]; [_messageImageView setAsset:asset]; @@ -105,7 +119,6 @@ _messageImageView.hidden = YES; _finalImage.hidden = NO; _fileView.hidden = YES; - _plusLongGestureRecognizer.enabled = YES; [self layoutSubviews]; }); } @@ -127,14 +140,10 @@ } - (void) loadFileAsset:(NSString *)name { - NSString *text = [NSString stringWithFormat:@"📎 %@",name]; - _fileName.text = text; - dispatch_async(dispatch_get_main_queue(), ^{ - _fileName.hidden = _fileView.hidden = _fileButton.hidden = NO; - _imageGestureRecognizer.enabled = NO; - _plusLongGestureRecognizer.enabled = NO; - _playButton.hidden = YES; - }); + UIImage *image = [UIChatBubbleTextCell getImageFromFileName:name]; + [self loadImageAsset:nil image:image]; + _imageGestureRecognizer.enabled = YES; + _finalImage.tag = FILE_ICON_TAG; } - (void) loadPlaceholder { @@ -145,38 +154,63 @@ [_messageImageView stopLoading]; _messageImageView.hidden = YES; _imageGestureRecognizer.enabled = YES; - _plusLongGestureRecognizer.enabled = YES; _finalImage.hidden = NO; [self layoutSubviews]; }); } + - (void)update { if (self.message == nil) { LOGW(@"Cannot update message room cell: NULL message"); return; } [super update]; + + _vrPlayPause.enabled = linphone_core_get_calls_nb(LC) == 0; + + NSMutableDictionary *encrptedFilePaths = NULL; + if ([VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId]) { + encrptedFilePaths = [LinphoneManager getMessageAppDataForKey:@"encryptedfiles" inMessage:self.message]; + if (!encrptedFilePaths) { + encrptedFilePaths = [NSMutableDictionary dictionary]; + } + } + + _voiceRecordingFile = nil; + LinphoneContent *voiceContent = [UIChatBubbleTextCell voiceContent:self.message]; + if (voiceContent) { + _voiceRecordingFile = [NSString stringWithUTF8String:[VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId] ? linphone_content_get_plain_file_path(voiceContent) : linphone_content_get_file_path(voiceContent)]; + if ([VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId]) + [encrptedFilePaths setValue:_voiceRecordingFile forKey:[NSString stringWithUTF8String:linphone_content_get_name(voiceContent)]]; + [self setVoiceMessageDuration]; + _vrWaveMaskPlayback.frame = CGRectZero; + _vrWaveMaskPlayback.backgroundColor = linphone_chat_message_is_outgoing(self.message) ? UIColor.orangeColor : UIColor.grayColor; + } + const bctbx_list_t *contents = linphone_chat_message_get_contents(self.message); + + size_t contentCount = bctbx_list_size(contents); + if (voiceContent) + contentCount--; BOOL multiParts = ((linphone_chat_message_get_text_content(self.message) != NULL) ? bctbx_list_size(contents) > 2 : bctbx_list_size(contents) > 1); + if (voiceContent && !multiParts) { + _cancelButton.hidden = _fileTransferProgress.hidden = _downloadButton.hidden = _playButton.hidden = _fileName.hidden = _fileView.hidden = _fileButton.hidden = YES; + return; + } + if (multiParts) { if (!assetIsLoaded) { - NSMutableDictionary *encrptedFilePaths = NULL; - if ([VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId]) { - encrptedFilePaths = [LinphoneManager getMessageAppDataForKey:@"encryptedfiles" inMessage:self.message]; - if (!encrptedFilePaths) { - encrptedFilePaths = [NSMutableDictionary dictionary]; - } - } - _imageGestureRecognizer.enabled = NO; _cancelButton.hidden = _fileTransferProgress.hidden = _downloadButton.hidden = _playButton.hidden = _fileName.hidden = _fileView.hidden = _fileButton.hidden = YES; - const bctbx_list_t *it = contents; int i; for (it = contents, i=0; it != NULL; it=bctbx_list_next(it)){ LinphoneContent *content = (LinphoneContent *)it->data; + if (linphone_content_is_voice_recording(content)) { // Handled elsewhere + continue; + } if (linphone_content_is_file_transfer(content) || linphone_content_is_file(content)){ UIChatContentView *contentView = [[UIChatContentView alloc] initWithFrame: CGRectMake(0,0,0,0)]; if([VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId] && (linphone_chat_message_is_outgoing(self.message) || linphone_content_is_file(content))) { @@ -214,8 +248,6 @@ return; } - - const char *url = linphone_chat_message_get_external_body_url(self.message); BOOL is_external = (url && (strstr(url, "http") == url)) || linphone_chat_message_get_file_transfer_information(self.message); @@ -241,10 +273,15 @@ _playButton.hidden = YES; _fileName.hidden = _fileView.hidden = _fileButton.hidden =YES; } else { - _downloadButton.hidden = NO; + _downloadButton.hidden = YES; + UIChatContentView * contentView = [[UIChatContentView alloc] init]; + [contentView setContent:fileContent message:self.message]; + contentView.position = 0; + [_contentViews addObject:contentView]; _cancelButton.hidden = _fileTransferProgress.hidden = YES; _playButton.hidden = YES; _fileName.hidden = _fileView.hidden = _fileButton.hidden = YES; + [self layoutSubviews]; } return; } @@ -464,25 +501,6 @@ }]; } -- (IBAction)onPlusClick:(id)sender { - UILongPressGestureRecognizer *gesture = (UILongPressGestureRecognizer *)sender; - if (gesture.state != UIGestureRecognizerStateBegan) { - // allow only one click once time - return; - } - DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:@""]; - dispatch_async(dispatch_get_main_queue(), ^{ - [sheet addButtonWithTitle:NSLocalizedString(@"Save to Gallery", nil) - block:^() { - LinphoneContent *content = linphone_chat_message_get_file_transfer_information(self.message); - NSString *name = [NSString stringWithUTF8String:linphone_content_get_name(content)]; - [ChatConversationView writeMediaToGallery:name fileType:[NSString stringWithUTF8String:linphone_content_get_type(content)?:""]]; - }]; - - [sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil]; - [sheet showInView:PhoneMainView.instance.view]; - }); -} - (IBAction)onFileClick:(id)sender { ChatConversationView *view = VIEW(ChatConversationView); @@ -510,21 +528,15 @@ } } -- (void)onResendClick:(id)event { - if (_downloadButton.hidden == NO) { - // if download button is displayed, click on it - [self onDownloadClick:event]; - } else if (_cancelButton.hidden == NO) { - [self onCancelClick:event]; - } else { - [super onResend]; - } -} - (IBAction)onImageClick:(id)event { + if (_finalImage.tag == FILE_ICON_TAG) { + [self onFileClick:nil]; + return; + } LinphoneChatMessageState state = linphone_chat_message_get_state(self.message); if (state == LinphoneChatMessageStateNotDelivered) { - [self onResendClick:event]; + return; } else { if (![_messageImageView isLoading]) { ImageView *view = VIEW(ImageView); @@ -662,7 +674,7 @@ CGFloat max_imagesh=0; CGFloat max_imagesw=0; CGFloat originy=0; - CGFloat originx=0; + CGFloat originx=-IMAGE_DEFAULT_MARGIN; CGFloat availableWidth = chatTableView.tableView.frame.size.width-CELL_IMAGE_X_MARGIN; NSMutableArray *fileUrls = [[NSMutableArray alloc] init]; @@ -713,16 +725,111 @@ textFrame.origin = CGPointMake(textFrame.origin.x, self.finalAssetView.frame.origin.y + self.finalAssetView.frame.size.height); else // When image hasn't be download - textFrame.origin = CGPointMake(textFrame.origin.x, _imageSubView.frame.size.height + _imageSubView.frame.origin.y - 10); + textFrame.origin = CGPointMake(textFrame.origin.x, _voiceRecordingFile ? _fileView.frame.origin.y : _imageSubView.frame.size.height + _imageSubView.frame.origin.y - 10); if (!utf8Text) { textFrame.size.height = 0; } else { textFrame.size.height = bubbleFrame.size.height - 90;//textFrame.origin.x; } + + if (_voiceRecordingFile) { + CGRect vrFrame = _vrView.frame; + vrFrame.origin.y = _contentViews.count == 0 && !utf8Text ? _fileView.frame.origin.y : textFrame.origin.y; + _vrView.frame = vrFrame; + textFrame.origin.y += VOICE_RECORDING_PLAYER_HEIGHT; + _vrView.hidden = NO; + } else { + _vrView.hidden = YES; + } + + CGRect r = super.photoCellContentView.frame; + r.origin.y = linphone_chat_message_is_reply(super.message) ? super.replyView.view.frame.origin.y + super.replyView.view.frame.size.height + 10 : 7 ; + super.photoCellContentView.frame = r; + + r = super.photoCellContentView.frame; + r.origin.y = linphone_chat_message_is_forward(super.message) ? super.contactDateLabel.frame.origin.y + super.contactDateLabel.frame.size.height + 3 : r.origin.y; + super.photoCellContentView.frame = r; self.messageText.frame = textFrame; } +// Voice messages + +static AVAudioPlayer* utilityPlayer; + +-(void) setVoiceMessageDuration { + NSError *error = nil; + AVAudioPlayer* utilityPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL URLWithString:_voiceRecordingFile] error:&error]; // Workaround as opening multiple linphone_players at the same time can cause crash (here for example layout refreshed whilst a voice memo is playing + _vrTimerLabel.text = [self formattedDuration:utilityPlayer.duration]; + utilityPlayer = nil; +} + +-(void) voicePlayTimerUpdate { + CGRect r = _vrWaveMaskPlayback.frame; + r.size.width += _vrView.frame.size.width / ((linphone_player_get_duration(voicePlayer) / 500)) ; + if (r.size.width > _vrView.frame.size.width) { + r.size.width = _vrView.frame.size.width; + } + [UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{ + _vrWaveMaskPlayback.frame = r; + }completion:^(BOOL finished) {}]; +} + + +-(void) stopPlayer { + [NSNotificationCenter.defaultCenter removeObserver:self]; + [chatView stopSharedPlayer]; + [_vrPlayPause setImage:[UIImage imageNamed:@"vr_play"] forState:UIControlStateNormal]; + [_vrPlayerTimer invalidate]; + _vrWaveMaskPlayback.frame = CGRectZero; +} + +-(NSString *)formattedDuration:(long)valueMs { + return [NSString stringWithFormat:@"%02ld:%02ld", valueMs/ 60, (valueMs % 60) ]; +} + +-(void) startPlayer { + [chatView startSharedPlayer:_voiceRecordingFile.UTF8String]; + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(stopPlayer) + name:kLinphoneVoiceMessagePlayerLostFocus + object:nil]; + + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(stopPlayer) + name:kLinphoneVoiceMessagePlayerEOF + object:nil]; + + [_vrPlayPause setImage:[UIImage imageNamed:@"vr_stop"] forState:UIControlStateNormal]; + CGRect r = CGRectZero; + r.size.height = _vrView.frame.size.height - 14; + r.origin.y = 7; + _vrWaveMaskPlayback.frame = r; + _vrPlayerTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 + target:self + selector:@selector(voicePlayTimerUpdate) + userInfo:nil + repeats:YES]; + [self voicePlayTimerUpdate]; + +} + +- (IBAction)onVRPlayPauseClick:(id)sender { + if ([chatView sharedPlayedIsPlaying:_voiceRecordingFile.UTF8String]) + [self stopPlayer]; + else { + [self startPlayer]; + } +} + + +// menu + +-(void) onPopupMenuPressed { + [super onPopupMenuPressed]; +} + + @end diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.h b/Classes/LinphoneUI/UIChatBubbleTextCell.h index 6778a8143..fe729b670 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.h +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.h @@ -22,12 +22,16 @@ #import "UITextViewNoDefine.h" #import "ChatConversationTableView.h" #import "UIRoundedImageView.h" +#import "UIChatReplyBubbleView.h" #define CELL_IMAGE_X_MARGIN 100 #define IMAGE_DEFAULT_WIDTH 120 #define IMAGE_DEFAULT_MARGIN 5 +#define VOICE_RECORDING_PLAYER_HEIGHT 60 +#define VOICE_RECORDING_PLAYER_WIDTH 300 -@interface UIChatBubbleTextCell : UITableViewCell + +@interface UIChatBubbleTextCell : UITableViewCell @property(readonly, nonatomic) LinphoneEventLog *event; @property(readonly, nonatomic) LinphoneChatMessage *message; @@ -47,6 +51,22 @@ @property (weak, nonatomic) IBOutlet UIView *innerView; @property (weak, nonatomic) IBOutlet UILabel *ephemeralTime; @property (weak, nonatomic) IBOutlet UIImageView *ephemeralIcon; +@property ChatConversationTableView *tableController; +@property BOOL popupMenuAllowed; + +// Message popup menu +@property UITableView *popupMenu; +@property NSMutableArray *messageActionsTitles; +@property NSMutableArray *messageActionsIcons; +@property NSMutableArray *messageActionsBlocks; + +// Message reply/transfer +@property UIChatReplyBubbleView *replyView; +@property UILabel *replyOrForward; +@property (weak, nonatomic) IBOutlet UIImageView *replyTransferIcon; +@property (weak, nonatomic) IBOutlet UILabel *replyTransferLabel; +@property (weak, nonatomic) IBOutlet UIView *photoCellContentView; + @property(nonatomic) BOOL isFirst; @property(nonatomic) BOOL isLast; @@ -57,14 +77,13 @@ + (CGSize)getMediaMessageSizefromOriginalSize:(CGSize)originalSize withWidth:(int)width; + (UIImage *)getImageFromVideoUrl:(NSURL *)url; + (UIImage *)getImageFromContent:(LinphoneContent *)content filePath:(NSString *)filePath; ++ (UIImage *)getImageFromFileName:(NSString *)fileName; - (void)setEvent:(LinphoneEventLog *)event; - (void)setChatMessageForCbs:(LinphoneChatMessage *)message; - (void)clearEncryptedFiles; - (void)onDelete; -- (void)onResend; -- (void)onLime; - (void)update; - (void)displayImdmStatus:(LinphoneChatMessageState)state; @@ -72,5 +91,7 @@ + (NSString *)TextMessageForChat:(LinphoneChatMessage *)message; + (CGSize)computeBoundingBox:(NSString *)text size:(CGSize)size font:(UIFont *)font; + (NSString *)ContactDateForChat:(LinphoneChatMessage *)message; - ++(LinphoneContent *) voiceContent:(LinphoneChatMessage *)message; +-(void) onPopupMenuPressed; +-(void) dismissPopup; @end diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.m b/Classes/LinphoneUI/UIChatBubbleTextCell.m index 808cb20b1..ef170ae1b 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.m +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.m @@ -20,6 +20,7 @@ #import "UIChatBubbleTextCell.h" #import "LinphoneManager.h" #import "PhoneMainView.h" +#import "Utils.h" #import #import @@ -28,6 +29,8 @@ #pragma mark - Lifecycle Functions + + - (id)initWithIdentifier:(NSString *)identifier { if ((self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]) != nil) { if ([identifier isEqualToString:NSStringFromClass(self.class)]) { @@ -40,22 +43,11 @@ [self addSubview:sub]; } } + + + [_innerView addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onPopupMenuPressed)]]; + _messageText.userInteractionEnabled = false; - UITapGestureRecognizer *limeRecognizer = - [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onLime)]; - limeRecognizer.numberOfTapsRequired = 1; - //[_LIMEKO addGestureRecognizer:limeRecognizer]; - //_LIMEKO.userInteractionEnabled = YES; - UITapGestureRecognizer *resendRecognizer = - [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onResend)]; - resendRecognizer.numberOfTapsRequired = 1; - [_bubbleView addGestureRecognizer:resendRecognizer]; - _imdmIcon.userInteractionEnabled = YES; - UITapGestureRecognizer *resendRecognizer2 = - [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onResend)]; - resendRecognizer2.numberOfTapsRequired = 1; - //[_imdmLabel addGestureRecognizer:resendRecognizer2]; - //_imdmLabel.userInteractionEnabled = YES; self.contentView.userInteractionEnabled = NO; return self; @@ -205,11 +197,12 @@ _avatarImage.hidden = !_isFirst; } + // Not use [UIImage imageNamed], it takes too much time - NSString *imageName = outgoing ? @"color_A.png" : @"color_D.png"; - _backgroundColorImage.image = - [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/%@",[[NSBundle mainBundle] bundlePath],imageName]]; - + _backgroundColorImage.image = nil; + _backgroundColorImage.backgroundColor = outgoing ? [UIColor color:@"A"] : [UIColor color:@"D"]; + + // set maskedCorners if (@available(iOS 11.0, *)) { _backgroundColorImage.layer.cornerRadius = 10; @@ -255,6 +248,7 @@ frame.origin.y = _isFirst ? 20 : 0; _innerView.frame = frame; + [_messageText setAccessibilityLabel:outgoing ? @"Outgoing message" : @"Incoming message"]; if (outgoing && (state == LinphoneChatMessageStateDeliveredToUser || state == LinphoneChatMessageStateDisplayed || @@ -262,6 +256,23 @@ [self displayImdmStatus:state]; } else [self displayImdmStatus:LinphoneChatMessageStateInProgress]; + + if (linphone_chat_message_is_reply(_message)) { + if (_replyView == nil) { + _replyView = [[UIChatReplyBubbleView alloc] initWithNibName:@"UIChatReplyBubbleView" bundle:nil]; + [self.innerView addSubview:_replyView.view]; + } + _replyView.view.hidden = false; + CGRect replyFrame = CGRectMake(_contactDateLabel.frame.origin.x, _contactDateLabel.frame.origin.y+_contactDateLabel.frame.size.height,self.contactDateLabel.frame.size.width, REPLY_CHAT_BUBBLE_HEIGHT); + _replyView.view.frame = replyFrame; + [_replyView configureForMessage:linphone_chat_message_get_reply_message(_message) withDimissBlock:^{} hideDismiss:true withClickBlock:^{ + [_tableController scrollToMessage:linphone_chat_message_get_reply_message(_message)]; + }]; + } else { + if (_replyView) + _replyView.view.hidden = true; + } + } - (void)setEditing:(BOOL)editing { @@ -269,7 +280,6 @@ } - (void)setEditing:(BOOL)editing animated:(BOOL)animated { - _messageText.userInteractionEnabled = !editing; _resendRecognizer.enabled = !editing; } @@ -300,76 +310,6 @@ } } -- (void)onLime { - /*if (!_LIMEKO.hidden) - [self displayLIMEWarning];*/ -} - -- (void)onResend { - if (_message == nil || !linphone_chat_message_is_outgoing(_message)) - return; - - LinphoneChatMessageState state = linphone_chat_message_get_state(_message); - if (state != LinphoneChatMessageStateNotDelivered && state != LinphoneChatMessageStateFileTransferError) - return; - - const bctbx_list_t *contents = linphone_chat_message_get_contents(_message); - BOOL multiParts = ((linphone_chat_message_get_text_content(self.message) != NULL) ? bctbx_list_size(contents) > 2 : bctbx_list_size(contents) > 1); - if (multiParts) { - FileContext *newfileContext = [[FileContext alloc] init]; - [newfileContext clear]; - NSMutableDictionary *encrptedFilePaths = encrptedFilePaths = [LinphoneManager getMessageAppDataForKey:@"encryptedfiles" inMessage:_message]; - int i; - const bctbx_list_t *it; - for (it = contents, i=0; it != NULL; it=bctbx_list_next(it)){ - LinphoneContent *content = (LinphoneContent *)it->data; - if (linphone_content_is_file_transfer(content) || linphone_content_is_file(content)){ - NSString *name = [NSString stringWithUTF8String:linphone_content_get_name(content)]; - NSString *filePath = [encrptedFilePaths valueForKey:name]; - if (filePath == NULL) { - filePath = [LinphoneManager validFilePath:name]; - } - [newfileContext addObject:[NSData dataWithContentsOfFile:filePath] name:name type:[NSString stringWithUTF8String:linphone_content_get_type(content)]]; - } - } - [self onDelete]; - dispatch_async(dispatch_get_main_queue(), ^ { - const char *text = linphone_chat_message_get_text_content(_message); - [_chatRoomDelegate resendMultiFiles:newfileContext message: text? [NSString stringWithUTF8String:text]: NULL]; - }); - return; - } - if (linphone_chat_message_get_file_transfer_information(_message) != NULL) { - NSString *localImage = [LinphoneManager getMessageAppDataForKey:@"localimage" inMessage:_message]; - NSString *localVideo = [LinphoneManager getMessageAppDataForKey:@"localvideo" inMessage:_message]; - NSString *localFile = [LinphoneManager getMessageAppDataForKey:@"localfile" inMessage:_message]; - NSString *filePath = [LinphoneManager getMessageAppDataForKey:@"encryptedfile" inMessage:self.message]; - - [self onDelete]; - dispatch_async(dispatch_get_main_queue(), ^ { - NSData *data = NULL; - if (filePath) { - data = [NSData dataWithContentsOfFile:filePath]; - } - const char *text = linphone_chat_message_get_text_content(_message); - NSString *str = text ? [NSString stringWithUTF8String:text] : NULL; - if (localImage) { - [_chatRoomDelegate resendFile: (data?:[ChatConversationView getFileData:localImage]) withName:localImage type:@"image" key:@"localimage" message:str]; - } else if (localVideo) { - [_chatRoomDelegate resendFile:(data?:[ChatConversationView getFileData:localVideo]) withName:localVideo type:@"video" key:@"localvideo" message:str]; - } else { - [_chatRoomDelegate resendFile:(data?:[ChatConversationView getFileData:localFile]) withName:localFile type:@"image" key:@"localfile" message:str]; - } - }); - } else { - [self onDelete]; - double delayInSeconds = 0.4; - dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); - dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { - [_chatRoomDelegate resendChat:self.textMessage withExternalUrl:nil]; - }); - } -} #pragma mark - State changed handling static void message_status(LinphoneChatMessage *msg, LinphoneChatMessageState state) { LOGI(@"State for message [%p] changed to %s", msg, linphone_chat_message_state_to_string(state)); @@ -424,9 +364,15 @@ static const CGFloat CELL_MIN_HEIGHT = 65.0f; static const CGFloat CELL_MIN_WIDTH = 190.0f; static const CGFloat CELL_MESSAGE_X_MARGIN = 68 + 10.0f; static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; +static const CGFloat REPLY_CHAT_BUBBLE_HEIGHT = 120; +static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18; + + (CGSize)ViewHeightForMessage:(LinphoneChatMessage *)chat withWidth:(int)width { - return [self ViewHeightForMessageText:chat withWidth:width textForImdn:nil]; + CGSize size = [self ViewHeightForMessageText:chat withWidth:width textForImdn:nil]; + size.height += linphone_chat_message_is_forward(chat) || linphone_chat_message_is_reply(chat) ? REPLY_OR_FORWARD_TAG_HEIGHT : 0; + size.height += linphone_chat_message_is_reply(chat) ? REPLY_CHAT_BUBBLE_HEIGHT+5 : 0; + return size; } + (CGSize)ViewHeightForFile:(int)width { @@ -437,6 +383,41 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; return size; } + ++(NSString *)formattedDuration:(long)valueMs { + return [NSString stringWithFormat:@"%02ld:%02ld", valueMs/ 60, (valueMs % 60) ]; +} + ++(NSString *) recordingDuration:(NSString *) _voiceRecordingFile{ + NSError *error = nil; + AVAudioPlayer* utilityPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL URLWithString:_voiceRecordingFile] error:&error]; // Workaround as opening multiple linphone_players at the same time can cause crash (here for example layout refreshed whilst a voice memo is playing + return [self formattedDuration:utilityPlayer.duration]; + utilityPlayer = nil; +} + ++ (UIImage *)getImageFromFileName:(NSString *)fileName { + NSString *extension = [[fileName.lowercaseString componentsSeparatedByString:@"."] lastObject]; + UIImage *image; + NSString * text = fileName; + if ([fileName containsString:@"voice-recording"]) { + image = [UIImage imageNamed:@"file_voice_default"]; + text = [self recordingDuration:[LinphoneManager validFilePath:fileName]]; + } else { + if ([extension isEqualToString:@"pdf"]) + image = [UIImage imageNamed:@"file_pdf_default"]; + else if ([@[@"png", @"jpg", @"jpeg", @"bmp", @"heic"] containsObject:extension]) + image = [UIImage imageNamed:@"file_picture_default"]; + else if ([@[@"mkv", @"avi", @"mov", @"mp4"] containsObject:extension]) + image = [UIImage imageNamed:@"file_video_default"]; + else if ([@[@"wav", @"au", @"m4a"] containsObject:extension]) + image = [UIImage imageNamed:@"file_audio_default"]; + else + image = [UIImage imageNamed:@"file_default"]; + } + + return [SwiftUtil textToImageWithDrawText:text inImage:image]; +} + + (UIImage *)getImageFromContent:(LinphoneContent *)content filePath:(NSString *)filePath; { NSString *type = [NSString stringWithUTF8String:linphone_content_get_type(content)]; NSString *name = [NSString stringWithUTF8String:linphone_content_get_name(content)]; @@ -452,11 +433,23 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; image = [[UIImage alloc] initWithData:data]; } if (image) return image; - UIImage *basicImage = [ChatConversationView getBasicImage]; - image = [ChatConversationView drawText:[NSString stringWithFormat:@"📎 %@",name] image:basicImage textSize:25]; - return image; + else return [self getImageFromFileName:name]; } ++(LinphoneContent *) voiceContent:(LinphoneChatMessage *)message { + for (const bctbx_list_t *it = linphone_chat_message_get_contents(message); it != NULL; it=bctbx_list_next(it)){ + LinphoneContent *content = (LinphoneContent *)it->data; + if (linphone_content_is_voice_recording(content)) + return content; + } + return nil; +} + + ++(CGSize) addVoicePlayerToSize:(CGSize)size withMargins:(BOOL)margins { + return CGSizeMake(MAX(size.width,VOICE_RECORDING_PLAYER_WIDTH + (margins ? CELL_MESSAGE_X_MARGIN: 0)), size.height + VOICE_RECORDING_PLAYER_HEIGHT+(margins ? CELL_MESSAGE_Y_MARGIN: 0)); + +} + (CGSize)ViewHeightForMessageText:(LinphoneChatMessage *)chat withWidth:(int)width textForImdn:(NSString *)imdnText { NSString *messageText = [UIChatBubbleTextCell TextMessageForChat:chat]; @@ -484,14 +477,51 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; CGFloat imagesh=0; CGFloat max_imagesw=0; CGFloat max_imagesh=0; + LinphoneContent *voiceContent = [self voiceContent:chat]; const bctbx_list_t *contents = linphone_chat_message_get_contents(chat); - BOOL multiParts = ((linphone_chat_message_get_text_content(chat) != NULL) ? bctbx_list_size(contents) > 2 : bctbx_list_size(contents) > 1); + size_t contentCount = bctbx_list_size(contents); + if (voiceContent) + contentCount--; + + BOOL multiParts = ((linphone_chat_message_get_text_content(chat) != NULL) ? contentCount > 2 : contentCount > 1); + + if (voiceContent && contentCount == 0) { + size = CGSizeMake(VOICE_RECORDING_PLAYER_WIDTH, VOICE_RECORDING_PLAYER_HEIGHT); + CGSize textSize = CGSizeMake(0, 0); + if (![messageText isEqualToString:@"🗻"]) { + textSize = [self computeBoundingBox:messageText + size:CGSizeMake(max_imagesw , CGFLOAT_MAX) + font:messageFont]; + } + + // add size for message text + size.height += textSize.height; + size.width = MAX(textSize.width, size.width); + size.width = MAX(size.width + CELL_MESSAGE_X_MARGIN, CELL_MIN_WIDTH); + size.height = MAX(size.height + CELL_MESSAGE_Y_MARGIN, CELL_MIN_HEIGHT) ; + return size; + } + if (multiParts) { const bctbx_list_t *it = contents; NSMutableDictionary *encrptedFilePaths = [LinphoneManager getMessageAppDataForKey:@"encryptedfiles" inMessage:chat]; for (it = contents; it != NULL; it=bctbx_list_next(it)){ LinphoneContent *content = (LinphoneContent *)it->data; + if (linphone_content_is_voice_recording(content)) { + CGSize sSize = CGSizeMake(VOICE_RECORDING_PLAYER_WIDTH, VOICE_RECORDING_PLAYER_HEIGHT); + imagesw += sSize.width; + if (imagesw > width) { + imagesw = sSize.width; + max_imagesw = MAX(max_imagesw, imagesw); + max_imagesh += imagesh; + imagesh = sSize.height; + } else { + max_imagesw = MAX(max_imagesw, imagesw); + imagesh = MAX(imagesh, sSize.height); + } + continue; + } UIImage *image; if(!linphone_chat_message_is_outgoing(chat) && linphone_content_is_file_transfer(content)) { // not yet downloaded @@ -538,22 +568,25 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; size.height = MAX(size.height + CELL_MESSAGE_Y_MARGIN, CELL_MIN_HEIGHT) ; return size; } + - - LinphoneContent *fileContent = linphone_chat_message_get_file_transfer_information(chat); - if (url == nil && fileContent == NULL) { + // if here, either 1 file + text or just one file or just text. + BOOL justText = linphone_chat_message_get_text_content(chat) != NULL && contentCount == 1; + if (justText) { // Just text size = [self computeBoundingBox:messageText size:CGSizeMake(width - CELL_MESSAGE_X_MARGIN - 4, CGFLOAT_MAX) font:messageFont]; - } else { + size.width += 4; + } else { // Just file or file with text + LinphoneContent *fileContent = linphone_chat_message_get_file_transfer_information(chat); NSString *localImage = [LinphoneManager getMessageAppDataForKey:@"localimage" inMessage:chat]; NSString *localFile = [LinphoneManager getMessageAppDataForKey:@"localfile" inMessage:chat]; NSString *localVideo = [LinphoneManager getMessageAppDataForKey:@"localvideo" inMessage:chat]; NSString *filePath = [LinphoneManager getMessageAppDataForKey:@"encryptedfile" inMessage:chat]; - NSString *fileName = [NSString stringWithUTF8String:linphone_content_get_name(fileContent)]; - + NSString *fileName = fileContent ? [NSString stringWithUTF8String:linphone_content_get_name(fileContent)] : nil; + CGSize textSize = CGSizeMake(0, 0); - if (![messageText isEqualToString:@"🗻"]) { + if (![messageText isEqualToString:@"🗻"] && messageText.length > 0) { textSize = [self computeBoundingBox:messageText size:CGSizeMake(width - CELL_MESSAGE_X_MARGIN - 4, CGFLOAT_MAX) font:messageFont]; @@ -582,28 +615,45 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; NSData *data = [NSData dataWithContentsOfURL:[VIEW(ChatConversationView) getICloudFileUrl:localFile]]; image = [[UIImage alloc] initWithData:data]; } + } else if (voiceContent){ + return [self addVoicePlayerToSize:[self ViewHeightForFile:width] withMargins:true]; } else { - return [self ViewHeightForFile:width]; + image = nil; + originalImageSize = CGSizeMake(140, 140); } - - originalImageSize = image.size; + if (image != nil) + originalImageSize = image.size; } else { if (!localImage && !localVideo) { //We are loading the image - return CGSizeMake(CELL_MIN_WIDTH + CELL_MESSAGE_X_MARGIN, CELL_MIN_HEIGHT + CELL_MESSAGE_Y_MARGIN + textSize.height + 20); + CGSize baseSize = CGSizeMake(120 + CELL_MESSAGE_X_MARGIN, 120 + CELL_MESSAGE_Y_MARGIN + textSize.height + (textSize.height != 0 ? 20 : 0)); + if (voiceContent) { + baseSize = [self addVoicePlayerToSize:baseSize withMargins:true]; + baseSize.height -= VOICE_RECORDING_PLAYER_HEIGHT; + baseSize.height += 10; + } + return baseSize; } if (localImage && [[NSFileManager defaultManager] fileExistsAtPath:filePath]) { NSData* data = [NSData dataWithContentsOfFile:filePath]; UIImage *image = [[UIImage alloc] initWithData:data]; if (!image) { - return [self ViewHeightForFile:width]; + CGSize fileSize = [self ViewHeightForFile:width]; + if (voiceContent) { + fileSize = [self addVoicePlayerToSize:fileSize withMargins:true]; + } + return fileSize; } originalImageSize = image.size; } else if (localVideo && [[NSFileManager defaultManager] fileExistsAtPath:filePath]) { UIImage *image = [UIChatBubbleTextCell getImageFromVideoUrl:[NSURL fileURLWithPath:filePath]]; if (!image) { - return [self ViewHeightForFile:width]; + CGSize fileSize = [self ViewHeightForFile:width]; + if (voiceContent) { + fileSize = [self addVoicePlayerToSize:fileSize withMargins:true]; + } + return fileSize; } originalImageSize = image.size; } else { @@ -615,7 +665,11 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; assets = [PHAsset fetchAssetsWithLocalIdentifiers:[NSArray arrayWithObject:localVideo] options:nil]; if (![assets firstObject]) { - return CGSizeMake(CELL_MIN_WIDTH, CELL_MIN_WIDTH + CELL_MESSAGE_Y_MARGIN + textSize.height); + CGSize baseSize = CGSizeMake(CELL_MIN_WIDTH, CELL_MIN_WIDTH + CELL_MESSAGE_Y_MARGIN + textSize.height); + if (voiceContent) { + baseSize = [self addVoicePlayerToSize:baseSize withMargins:true]; + } + return baseSize; } else { PHAsset *asset = [assets firstObject]; originalImageSize = CGSizeMake([asset pixelWidth], [asset pixelHeight]); @@ -627,6 +681,11 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; size.height += textSize.height; size.width = MAX(textSize.width, size.width); } + + if (voiceContent) { + size.width = MAX(size.width,VOICE_RECORDING_PLAYER_WIDTH); + size.height += VOICE_RECORDING_PLAYER_HEIGHT; + } size.width = MAX(size.width + CELL_MESSAGE_X_MARGIN, CELL_MIN_WIDTH); size.height = MAX(size.height + CELL_MESSAGE_Y_MARGIN, CELL_MIN_HEIGHT); @@ -670,15 +729,45 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; int origin_x; bubbleFrame.size = [self.class ViewSizeForMessage:_message withWidth:available_width]; + if (linphone_chat_message_is_reply(_message)) { + bubbleFrame.size.width = MAX(bubbleFrame.size.width, 300); + } if (tableView.isEditing) { origin_x = 0; } else { origin_x = (is_outgoing ? self.frame.size.width - bubbleFrame.size.width : 0); } + + CGRect r = _messageText.frame; + r.origin.y = linphone_chat_message_is_reply(_message) ? _replyView.view.frame.origin.y + _replyView.view.frame.size.height + 5 : 3; + _messageText.frame = r; + + r = _messageText.frame; + r.origin.y = linphone_chat_message_is_forward(_message) ? _contactDateLabel.frame.origin.y + _contactDateLabel.frame.size.height + 5 : r.origin.y; + _messageText.frame = r; + + _replyTransferIcon.hidden = ! linphone_chat_message_is_reply(_message) && !linphone_chat_message_is_forward(_message); + _replyTransferLabel.hidden = ! linphone_chat_message_is_reply(_message) && !linphone_chat_message_is_forward(_message); + + if (linphone_chat_message_is_reply(_message)) { + CGRect replyFrame = CGRectMake(10, _replyTransferLabel.frame.origin.y+_replyTransferLabel.frame.size.height+5,MAX(self.contactDateLabel.frame.size.width-20,180), REPLY_CHAT_BUBBLE_HEIGHT); + _replyView.view.frame = replyFrame; + _replyTransferIcon.image = [UIImage imageNamed:@"menu_reply_default"]; + _replyTransferLabel.text = NSLocalizedString(@"Answer",nil); + _replyTransferLabel.textColor = [UIColor lightGrayColor]; + } + + if (linphone_chat_message_is_forward(_message)) { + _replyTransferIcon.image = [UIImage imageNamed:@"menu_forward_default"]; + _replyTransferLabel.text = NSLocalizedString(@"Transferred",nil); + _replyTransferLabel.textColor = [UIColor darkGrayColor]; + } bubbleFrame.origin.x = origin_x; _bubbleView.frame = bubbleFrame; + + } } @@ -706,4 +795,173 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; return mediaSize; } + +// Message popup menu +// Copy text -> if has text +// Transfer -> always +// Reply -> always +// IMDM Status -> out +// Delete -> always + + +-(void) buildActions { + LinphoneChatMessage *message = self.message; + _messageActionsTitles = [[NSMutableArray alloc] init]; + _messageActionsBlocks = [[NSMutableArray alloc] init]; + _messageActionsIcons = [[NSMutableArray alloc] init]; + + UIChatBubbleTextCell *thiz = self; + + LinphoneChatMessageState state = linphone_chat_message_get_state(self.message); + if (state == LinphoneChatMessageStateNotDelivered || state == LinphoneChatMessageStateFileTransferError) { + [_messageActionsTitles addObject:NSLocalizedString(@"Resend", nil)]; + [_messageActionsIcons addObject:@"menu_resend_default"]; + [_messageActionsBlocks addObject:^{ + [thiz dismissPopup]; + if (!linphone_core_is_network_reachable(LC)) { + [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView:@"send a message"] animated:YES completion:nil]; + return; + } + linphone_chat_message_send(message); + }]; + } + + + if (linphone_chat_message_get_utf8_text(message)) { + [_messageActionsTitles addObject:NSLocalizedString(@"Copy text", nil)]; + [_messageActionsIcons addObject:@"menu_copy_text_default"]; + [_messageActionsBlocks addObject:^{ + [thiz dismissPopup]; + [UIPasteboard.generalPasteboard setString:[NSString stringWithUTF8String:linphone_chat_message_get_text_content(message)]]; + }]; + } + + + [_messageActionsTitles addObject:NSLocalizedString(@"Forward", nil)]; + [_messageActionsIcons addObject:@"menu_forward_default"]; + [_messageActionsBlocks addObject:^{ + [thiz dismissPopup]; + VIEW(ChatConversationView).pendingForwardMessage = message; + [PhoneMainView.instance changeCurrentView:VIEW(ChatsListView).compositeViewDescription]; + }]; + + + + [_messageActionsTitles addObject:NSLocalizedString(@"Reply", nil)]; + [_messageActionsIcons addObject:@"menu_reply_default"]; + [_messageActionsBlocks addObject:^{ + [thiz dismissPopup]; + [VIEW(ChatConversationView) initiateReplyViewForMessage:message]; + }]; + + if (linphone_chat_message_is_outgoing(self.message) && linphone_chat_room_get_nb_participants(linphone_chat_message_get_chat_room(self.message)) > 1) { + [_messageActionsTitles addObject:NSLocalizedString(@"Infos", nil)]; + [_messageActionsIcons addObject:@"menu_info"]; + [_messageActionsBlocks addObject:^{ + [thiz dismissPopup]; + ChatConversationImdnView *view = VIEW(ChatConversationImdnView); + view.msg = message; + [PhoneMainView.instance changeCurrentView:view.compositeViewDescription]; + }]; + } + + [_messageActionsTitles addObject:NSLocalizedString(@"Delete", nil)]; + [_messageActionsIcons addObject:@"menu_delete"]; + [_messageActionsBlocks addObject:^{ + [thiz dismissPopup]; + linphone_chat_room_delete_message(linphone_chat_message_get_chat_room(message), message); + [VIEW(ChatConversationView).tableController reloadData]; + }]; +} + +-(void) onPopupMenuPressed { + if (_popupMenu != nil) + [self dismissPopup]; + + if (!self.popupMenuAllowed) + return; + + + [VIEW(ChatConversationView).tableController dismissMessagesPopups]; + self.innerView.layer.borderWidth = 3; + self.innerView.layer.borderColor = [UIColor color:@"A"].CGColor; + [self buildActions]; + int width = 250; + int cellHeight = 44; + int numberOfItems = (int) _messageActionsTitles.count; + CGRect screenRect = UIScreen.mainScreen.bounds; + int menuHeight = numberOfItems * cellHeight; + + CGRect frame = CGRectMake( + linphone_chat_message_is_outgoing(self.message) ? screenRect.size.width - width - 10 : 10, + (self.frame.origin.y + self.frame.size.height) - [VIEW(ChatConversationView).tableController .tableView contentOffset].y > screenRect.size.height /2 ? self.frame.origin.y - menuHeight - 10: self.frame.origin.y + self.frame.size.height, + width, + menuHeight); + + _popupMenu = [[UITableView alloc]initWithFrame:frame]; + _popupMenu.dataSource = self; + _popupMenu.delegate = self; + _popupMenu.layer.shadowColor = [UIColor lightGrayColor].CGColor; + _popupMenu.layer.shadowOpacity = 0.5; + _popupMenu.layer.shadowOffset = CGSizeZero; + _popupMenu.layer.shadowRadius = 5; + _popupMenu.layer.masksToBounds = false; + _popupMenu.tableFooterView = [UIView new]; + _popupMenu.editing = NO; + _popupMenu.userInteractionEnabled = true; + [_popupMenu reloadData]; + [VIEW(ChatConversationView).tableController.view addSubview:_popupMenu]; + UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapOutsideMenu:)]; + tapGestureRecognizer.cancelsTouchesInView = NO; + tapGestureRecognizer.numberOfTapsRequired = 1; + [VIEW(ChatConversationView).tableController.view addGestureRecognizer:tapGestureRecognizer]; +} + +-(void) dismissPopup { + if (!_popupMenu) + return; + [_popupMenu removeFromSuperview]; + _popupMenu = nil; + self.innerView.layer.borderWidth = 0; + [self setNeedsLayout]; +} + + +-(void) tapOutsideMenu:(UITapGestureRecognizer *) g { + CGPoint p = [g locationInView:VIEW(ChatConversationView).tableController.view]; + if (!CGRectContainsPoint(_popupMenu.frame,p)) { + [self dismissPopup]; + } +} + +-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + void (^ myblock)(void) = [_messageActionsBlocks objectAtIndex:indexPath.row]; + [self dismissPopup]; + myblock(); +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return [_messageActionsTitles count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [[UITableViewCell alloc] init]; + cell.imageView.image = [[UIImage imageNamed:[_messageActionsIcons objectAtIndex:indexPath.row]] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + cell.textLabel.text = [_messageActionsTitles objectAtIndex:indexPath.row]; + cell.imageView.contentMode = UIViewContentModeScaleAspectFit; + if ([[_messageActionsIcons objectAtIndex:indexPath.row] isEqualToString:@"menu_delete"]) { + cell.textLabel.textColor = UIColor.redColor; + cell.imageView.tintColor = UIColor.redColor; + } else { + cell.imageView.tintColor = PhoneMainView.instance.darkMode ? UIColor.whiteColor : UIColor.blackColor; + } + return cell; +} + + + @end diff --git a/Classes/LinphoneUI/UIChatCell.h b/Classes/LinphoneUI/UIChatCell.h index d5d0327b9..29343ea40 100644 --- a/Classes/LinphoneUI/UIChatCell.h +++ b/Classes/LinphoneUI/UIChatCell.h @@ -38,6 +38,8 @@ @property(weak, nonatomic) IBOutlet UILabel *unreadCountLabel; @property (weak, nonatomic) IBOutlet UIImageView *imdmIcon; @property (weak, nonatomic) IBOutlet UIImageView *ephemeral; +@property (weak, nonatomic) IBOutlet UIImageView *forwardIcon; + - (id)initWithIdentifier:(NSString*)identifier; diff --git a/Classes/LinphoneUI/UIChatCell.m b/Classes/LinphoneUI/UIChatCell.m index 299898dbf..3fc01de59 100644 --- a/Classes/LinphoneUI/UIChatCell.m +++ b/Classes/LinphoneUI/UIChatCell.m @@ -46,6 +46,7 @@ - (void)setChatRoom:(LinphoneChatRoom *)achat { chatRoom = achat; [self update]; + [self.forwardIcon setImageNamed:@"forward_message_default" tintColor:PhoneMainView.instance.darkMode ? UIColor.whiteColor : UIColor.darkGrayColor]; } #pragma mark - diff --git a/Classes/LinphoneUI/UIChatContentView.m b/Classes/LinphoneUI/UIChatContentView.m index 45882677f..3b5713e94 100644 --- a/Classes/LinphoneUI/UIChatContentView.m +++ b/Classes/LinphoneUI/UIChatContentView.m @@ -31,19 +31,20 @@ if(!linphone_chat_message_is_outgoing(_message) && linphone_content_is_file_transfer(_content)) { // has not yet downloaded - UIImage *basicImage = [ChatConversationView getBasicImage]; NSString *name = [NSString stringWithUTF8String:linphone_content_get_name(content)] ; - UIImage *image = [ChatConversationView drawText:name image:basicImage textSize:25]; + UIImage *image = [UIChatBubbleTextCell getImageFromFileName:name]; [self setImage:image]; _downloadButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_downloadButton addTarget:self action:@selector(onDownloadClick:) forControlEvents:UIControlEventTouchUpInside]; - _downloadButton.backgroundColor = [UIColor orangeColor]; - UIFont *boldFont = [UIFont systemFontOfSize:10]; - NSMutableAttributedString *boldText = [[NSMutableAttributedString alloc] initWithString:@"Download" attributes:@{ NSFontAttributeName : boldFont }]; + UIFont *boldFont = [UIFont systemFontOfSize:12]; + NSMutableParagraphStyle * paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + paragraphStyle.alignment = NSTextAlignmentCenter; + + NSMutableAttributedString *boldText = [[NSMutableAttributedString alloc] initWithString:@"Download" attributes:@{ NSFontAttributeName : boldFont, NSParagraphStyleAttributeName:paragraphStyle,NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle) }]; [_downloadButton setAttributedTitle:boldText forState:UIControlStateNormal]; - _downloadButton.frame = CGRectMake(3, 3, 60, 30); + _downloadButton.frame = CGRectMake(0, 90, 120, 30); [self addSubview:_downloadButton]; } else { if (_filePath == NULL) { @@ -56,6 +57,7 @@ tapGestureRecognizer.numberOfTapsRequired = 1; tapGestureRecognizer.enabled = YES; [self addGestureRecognizer:tapGestureRecognizer]; + self.userInteractionEnabled = true; } } diff --git a/Classes/LinphoneUI/UIChatReplyBubbleView.h b/Classes/LinphoneUI/UIChatReplyBubbleView.h new file mode 100644 index 000000000..b9c175ae2 --- /dev/null +++ b/Classes/LinphoneUI/UIChatReplyBubbleView.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * 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, see . + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIChatReplyBubbleView : UIViewController +@property (weak, nonatomic) IBOutlet UILabel *senderName; +@property (weak, nonatomic) IBOutlet UIButton *dismissButton; +@property (weak, nonatomic) IBOutlet UIView *leftBar; +@property (weak, nonatomic) IBOutlet UIView *rightBar; +@property LinphoneChatMessage *message; +@property (weak, nonatomic) IBOutlet UILabel *textContent; +@property void (^ dismissAction)(void); +@property void (^ clickAction)(void); +@property (weak, nonatomic) IBOutlet UICollectionView *contentCollection; +@property NSArray *dataContent; +@property (weak, nonatomic) IBOutlet UILabel *originalMessageGone; + +-(void) configureForMessage:(LinphoneChatMessage *)message withDimissBlock:(void (^)(void))dismissBlock hideDismiss:(BOOL)hideDismiss withClickBlock:(void (^)(void))clickBlock; +@end + +NS_ASSUME_NONNULL_END diff --git a/Classes/LinphoneUI/UIChatReplyBubbleView.m b/Classes/LinphoneUI/UIChatReplyBubbleView.m new file mode 100644 index 000000000..e6e523561 --- /dev/null +++ b/Classes/LinphoneUI/UIChatReplyBubbleView.m @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * 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, see . + */ + +#import "UIChatReplyBubbleView.h" +#import "linphoneapp-Swift.h" +#import "Utils.h" + +@interface UIChatReplyBubbleView () + +@end + +@implementation UIChatReplyBubbleView + + +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + return [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + return self; +} + +-(void) viewDidLoad { + _contentCollection.dataSource = self; + [_contentCollection registerClass:UICollectionViewCell.class forCellWithReuseIdentifier:@"dataContent"]; +} + + +-(void) configureForMessage:(LinphoneChatMessage *)message withDimissBlock:(void (^)(void))dismissBlock hideDismiss:(BOOL)hideDismiss withClickBlock:(void (^)(void))clickBlock{ + if (!message) { + _textContent.hidden = true; + _dismissButton.hidden = true; + _contentCollection.hidden = true; + _senderName.hidden = true; + _originalMessageGone.hidden = false; + return; + } + if (hideDismiss) { + self.view.layer.cornerRadius = 10; + self.view.layer.masksToBounds = true; + } + _originalMessageGone.hidden = true; + self.message = message; + self.dataContent = [self loadDataContent]; + NSString *sender = [FastAddressBook displayNameForAddress:linphone_chat_message_get_from_address(message)]; + _senderName.text = sender; + const char * text = linphone_chat_message_get_text_content(message); + if (text && strlen(text) == 0) + text = nil; + _textContent.text = text ? [NSString stringWithUTF8String:text] : @""; + _dismissButton.hidden = hideDismiss; + _dismissAction = dismissBlock; + _clickAction = clickBlock; + if (hideDismiss) { + UITapGestureRecognizer *singleFingerTap = + [[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(onClick)]; + [self.view addGestureRecognizer:singleFingerTap]; + } + else + [_dismissButton addTarget:self action:@selector(dismissClick) forControlEvents:UIControlEventTouchUpInside]; + + + self.view.backgroundColor = hideDismiss ? UIColor.whiteColor :(linphone_chat_message_is_outgoing(message) ? [[UIColor color:@"A"] colorWithAlphaComponent:0.2] : [[UIColor color:@"D"] colorWithAlphaComponent:0.2]); + _leftBar.backgroundColor = linphone_chat_message_is_outgoing(message) ? [UIColor color:@"A"] : [UIColor color:@"D"]; + _leftBar.hidden = !hideDismiss; + _rightBar.backgroundColor = self.view.backgroundColor; + + + // Resize frame -> text or content only = 100, 145 otherwise + _contentCollection.hidden = self.dataContent.count == 0; + + CGRect r = self.view.frame ; + r.size.width = self.view.superview.frame.size.width; + self.view.frame = r; + + if (self.dataContent.count == 0) { + CGRect r = _textContent.frame; + r.origin.y = _contentCollection.frame.origin.y; + r.size.height = 87; + _textContent.frame = r; + } + + if (text == nil) { + CGRect r = _contentCollection.frame; + r.origin.y = 30; + _contentCollection.frame = r; + } +} + + +-(NSArray *) loadDataContent { + NSMutableArray *result = [[NSMutableArray alloc] init]; + const bctbx_list_t *contents = linphone_chat_message_get_contents(_message); + const char * text = linphone_chat_message_get_utf8_text(_message); + if (text && bctbx_list_size(contents) == 1) + return result; + + for (const bctbx_list_t * it = contents; it != NULL; it=bctbx_list_next(it)){ + LinphoneContent *content = (LinphoneContent *)it->data; + if (linphone_content_is_text(content)) + continue; + NSString *name = [NSString stringWithUTF8String:linphone_content_get_name(content)]; + NSMutableDictionary *encrptedFilePaths = encrptedFilePaths = [LinphoneManager getMessageAppDataForKey:@"encryptedfiles" inMessage:_message]; + NSString *filePath = encrptedFilePaths ? [encrptedFilePaths valueForKey:name] : nil; + if (filePath == NULL) { + filePath = [LinphoneManager validFilePath:name]; + } + [result addObject:[UIChatBubbleTextCell getImageFromContent:content filePath:filePath]]; + } + return result; +} + +-(void) dismissClick { + _dismissAction(); +} + +-(void) onClick { + _clickAction(); +} + +-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return 1; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return self.dataContent.count; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"dataContent" forIndexPath:indexPath]; + UIImageView *img = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 60, 60)]; + img.image = [self.dataContent objectAtIndex:indexPath.row]; + [cell.contentView addSubview:img]; + return cell; +} + + + + +@end diff --git a/Classes/LinphoneUI/UIConfirmationDialog.h b/Classes/LinphoneUI/UIConfirmationDialog.h index ad0a0b5b0..c2d9feadc 100644 --- a/Classes/LinphoneUI/UIConfirmationDialog.h +++ b/Classes/LinphoneUI/UIConfirmationDialog.h @@ -45,6 +45,7 @@ typedef void (^UIConfirmationBlock)(void); @property(weak, nonatomic) IBOutlet UIRoundBorderedButton *cancelButton; @property (weak, nonatomic) IBOutlet UIImageView *securityImage; +@property (weak, nonatomic) IBOutlet UIImageView *forwardImage; @property(weak, nonatomic) IBOutlet UIRoundBorderedButton *confirmationButton; @property (weak, nonatomic) IBOutlet UIView *authView; @property(weak, nonatomic) IBOutlet UILabel *titleLabel; diff --git a/Classes/LinphoneUI/UIImageViewDeletable.xib b/Classes/LinphoneUI/UIImageViewDeletable.xib index 2fa5d17e1..4d0dea67a 100644 --- a/Classes/LinphoneUI/UIImageViewDeletable.xib +++ b/Classes/LinphoneUI/UIImageViewDeletable.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -17,15 +15,15 @@ - + - + + - - + diff --git a/Classes/LinphoneUI/en.lproj/UIChatReplyBubbleView.strings b/Classes/LinphoneUI/en.lproj/UIChatReplyBubbleView.strings new file mode 100644 index 000000000..cfa28ba85 --- /dev/null +++ b/Classes/LinphoneUI/en.lproj/UIChatReplyBubbleView.strings @@ -0,0 +1,9 @@ + +/* Class = "UILabel"; text = "Original message removed"; ObjectID = "B26-sw-o4w"; */ +"B26-sw-o4w.text" = "Original message does not exist anymore."; + +/* Class = "UILabel"; text = "Label"; ObjectID = "Czc-VH-qiH"; */ +"Czc-VH-qiH.text" = "Label"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "uuW-tW-1Sj"; */ +"uuW-tW-1Sj.text" = "Label"; diff --git a/Classes/LinphoneUI/fr.lproj/UIChatReplyBubbleView.strings b/Classes/LinphoneUI/fr.lproj/UIChatReplyBubbleView.strings new file mode 100644 index 000000000..de5a5c443 --- /dev/null +++ b/Classes/LinphoneUI/fr.lproj/UIChatReplyBubbleView.strings @@ -0,0 +1,9 @@ + +/* Class = "UILabel"; text = "Original message removed"; ObjectID = "B26-sw-o4w"; */ +"B26-sw-o4w.text" = "Le message original n'existe plus."; + +/* Class = "UILabel"; text = "Label"; ObjectID = "Czc-VH-qiH"; */ +"Czc-VH-qiH.text" = "Label"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "uuW-tW-1Sj"; */ +"uuW-tW-1Sj.text" = "Label"; diff --git a/Classes/PhoneMainView.h b/Classes/PhoneMainView.h index 60633724e..adfe87b5a 100644 --- a/Classes/PhoneMainView.h +++ b/Classes/PhoneMainView.h @@ -121,6 +121,7 @@ - (BOOL)isIphoneXDevice; + (int)iphoneStatusBarHeight; +-(BOOL) darkMode; @end diff --git a/Classes/PhoneMainView.m b/Classes/PhoneMainView.m index c8d78cd4f..793243495 100644 --- a/Classes/PhoneMainView.m +++ b/Classes/PhoneMainView.m @@ -830,7 +830,7 @@ static RootViewManager *rootViewManagerInstance = nil; } if (!linphone_core_is_network_reachable(LC)) { - [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView] animated:YES completion:nil]; + [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView:@"send a message"] animated:YES completion:nil]; return; } @@ -957,4 +957,15 @@ void main_view_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomSta [controller dismissModalViewControllerAnimated:YES]; } +#pragma mark - Light/Dark mode + +-(BOOL) darkMode { + if (@available(iOS 13.0, *)) { + UITraitCollection *collection = [UITraitCollection currentTraitCollection]; + return collection.userInterfaceStyle == UIUserInterfaceStyleDark; + } else { + return false; + } +} + @end diff --git a/Classes/SwiftUtil.swift b/Classes/SwiftUtil.swift new file mode 100644 index 000000000..ff9278263 --- /dev/null +++ b/Classes/SwiftUtil.swift @@ -0,0 +1,69 @@ +/* +* Copyright (c) 2010-2020 Belledonne Communications SARL. +* +* This file is part of linphone-iphone +* +* 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, see . +*/ + +import UIKit + +@objc class SwiftUtil: NSObject { + + @objc static func textToImage(drawText text: String, inImage image: UIImage) -> UIImage { + let textColor = UIColor.black + let fontMax = UIFont.systemFont(ofSize: 30) + let backgroundColor = UIColor.white + + let size = CGSize(width: 120, height: 120) + + let scale = UIScreen.main.scale + UIGraphicsBeginImageContextWithOptions(size, false, scale) + let context = UIGraphicsGetCurrentContext() + backgroundColor.setFill() + context!.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height)) + + image.draw(in: CGRect(origin: CGPoint(x: size.width/2 - (image.size.width)/2,y: 5), size: image.size)) + + let label = UILabel(frame: CGRect(x: 0,y: 0,width: size.width,height: 50)) + label.numberOfLines = 0 + label.font = fontMax + label.adjustsFontSizeToFitWidth = true + label.text = text + label.textColor = textColor + label.textAlignment = .center + label.allowsDefaultTighteningForTruncation = true + label.lineBreakMode = .byTruncatingTail + imageWithLabel(label: label).draw(in: CGRect(origin: CGPoint(x:0,y: 60), size: CGSize(width: size.width,height: 50))) + let view = UIView(frame: CGRect(x: 0,y: 0,width: size.width,height: 50)) + view.addSubview(label) + label.sizeToFit() + + + let newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return newImage! + } + + static func imageWithLabel(label: UILabel) -> UIImage { + UIGraphicsBeginImageContextWithOptions(label.frame.size, false, 0.0) + label.layer.render(in: UIGraphicsGetCurrentContext()!) + let img = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + return img + } + +} + diff --git a/Classes/Utils/FileTransferDelegate.h b/Classes/Utils/FileTransferDelegate.h index 3aa3d22db..3fa22bb55 100644 --- a/Classes/Utils/FileTransferDelegate.h +++ b/Classes/Utils/FileTransferDelegate.h @@ -24,11 +24,10 @@ @interface FileTransferDelegate : NSObject -- (void)uploadFileContent: (FileContext *)context forChatRoom:(LinphoneChatRoom *)chatRoom; -- (void)uploadData:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom type:(NSString *)type subtype:(NSString *)subtype name:(NSString *)name key:(NSString *)key; +- (void)uploadFileContent: (FileContext *)context forChatRoom:(LinphoneChatRoom *)chatRoom rootMessage:(LinphoneChatMessage *)rootMessage; +- (void)uploadData:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom type:(NSString *)type subtype:(NSString *)subtype name:(NSString *)name key:(NSString *)key rootMessage:(LinphoneChatMessage *)rootMessage; - (void)uploadImage:(UIImage *)image forChatRoom:(LinphoneChatRoom *)chatRoom withQuality:(float)quality; -- (void)uploadFile:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom withName:(NSString *)name; -- (void)uploadVideo:(NSData *)data withassetId:(NSString *)phAssetId forChatRoom:(LinphoneChatRoom *)chatRoom; +- (void)uploadFile:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom withName:(NSString *)name rootMessage:(LinphoneChatMessage *)rootMessage; - (void)cancel; - (BOOL)download:(LinphoneChatMessage *)message; - (void)stopAndDestroy; diff --git a/Classes/Utils/FileTransferDelegate.m b/Classes/Utils/FileTransferDelegate.m index d8d20670b..0738c54b4 100644 --- a/Classes/Utils/FileTransferDelegate.m +++ b/Classes/Utils/FileTransferDelegate.m @@ -102,7 +102,7 @@ static void file_transfer_progress_indication_send(LinphoneChatMessage *message, } } -- (void)uploadData:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom type:(NSString *)type subtype:(NSString *)subtype name:(NSString *)name key:(NSString *)key{ +- (void)uploadData:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom type:(NSString *)type subtype:(NSString *)subtype name:(NSString *)name key:(NSString *)key rootMessage:(LinphoneChatMessage *)rootMessage{ if ([[LinphoneManager.instance fileTransferDelegates] containsObject:self]) { LOGW(@"fileTransferDelegates has already added %p", self); return; @@ -115,7 +115,8 @@ static void file_transfer_progress_indication_send(LinphoneChatMessage *message, linphone_content_set_subtype(content, [subtype UTF8String]); linphone_content_set_name(content, [name UTF8String]); linphone_content_set_file_path(content, [[LinphoneManager imagesDirectory] stringByAppendingPathComponent:name].UTF8String); - _message = linphone_chat_room_create_file_transfer_message(chatRoom, content); + _message = rootMessage; + linphone_chat_message_add_file_content(_message, content); BOOL isOneToOneChat = linphone_chat_room_get_capabilities(chatRoom) & LinphoneChatRoomCapabilitiesOneToOne; if (!isOneToOneChat && (_text!=nil && ![_text isEqualToString:@""])) linphone_chat_message_add_text_content(_message, [_text UTF8String]); @@ -124,15 +125,17 @@ static void file_transfer_progress_indication_send(LinphoneChatMessage *message, linphone_chat_message_cbs_set_file_transfer_progress_indication(linphone_chat_message_get_callbacks(_message), file_transfer_progress_indication_send); [LinphoneManager setValueInMessageAppData:name forKey:key inMessage:_message]; + + LOGI(@"%p Uploading content from message %p", self, _message); linphone_chat_message_send(_message); } -- (void)uploadFileContent: (FileContext *)context forChatRoom:(LinphoneChatRoom *)chatRoom { +- (void)uploadFileContent: (FileContext *)context forChatRoom:(LinphoneChatRoom *)chatRoom rootMessage:(LinphoneChatMessage *)rootMessage{ [LinphoneManager.instance.fileTransferDelegates addObject:self]; - _message = linphone_chat_room_create_empty_message(chatRoom); + _message = rootMessage; NSMutableArray *names = [[NSMutableArray alloc] init]; NSMutableArray *types = [[NSMutableArray alloc] init]; @@ -162,6 +165,7 @@ static void file_transfer_progress_indication_send(LinphoneChatMessage *message, // todo indication progress [LinphoneManager setValueInMessageAppData:names forKey:@"multiparts" inMessage:_message]; [LinphoneManager setValueInMessageAppData:types forKey:@"multipartstypes" inMessage:_message]; + LOGI(@"%p Uploading content from message %p", self, _message); linphone_chat_message_send(_message); } @@ -170,21 +174,17 @@ static void file_transfer_progress_indication_send(LinphoneChatMessage *message, - (void)uploadImage:(UIImage *)image forChatRoom:(LinphoneChatRoom *)chatRoom withQuality:(float)quality { NSString *name = [NSString stringWithFormat:@"%li-%f.jpg", (long)image.hash, [NSDate timeIntervalSinceReferenceDate]]; NSData *data = UIImageJPEGRepresentation(image, quality); - [self uploadData:data forChatRoom:chatRoom type:@"image" subtype:@"jpg" name:name key:@"localimage"]; + [self uploadData:data forChatRoom:chatRoom type:@"image" subtype:@"jpg" name:name key:@"localimage" rootMessage:nil]; } -- (void)uploadVideo:(NSData *)data withassetId:(NSString *)phAssetId forChatRoom:(LinphoneChatRoom *)chatRoom { - NSString *name = [NSString stringWithFormat:@"IMG-%f.MOV", [NSDate timeIntervalSinceReferenceDate]]; - [self uploadData:data forChatRoom:chatRoom type:@"video" subtype:@"mov" name:name key:@"localvideo"]; -} -- (void)uploadFile:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom withName:(NSString *)name { +- (void)uploadFile:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom withName:(NSString *)name rootMessage:(LinphoneChatMessage *)rootMessage { NSURL *url = [ChatConversationView getFileUrl:name]; AVAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil]; NSString *fileType = [[asset tracksWithMediaType:AVMediaTypeVideo] count] > 0 ? @"video" : @"file"; NSString *key = [ChatConversationView getKeyFromFileType:fileType fileName:name]; - [self uploadData:data forChatRoom:chatRoom type:fileType subtype:name.lastPathComponent name:name key:key]; + [self uploadData:data forChatRoom:chatRoom type:fileType subtype:name.lastPathComponent name:name key:key rootMessage:rootMessage]; } - (BOOL)download:(LinphoneChatMessage *)message { diff --git a/Classes/Utils/Utils.h b/Classes/Utils/Utils.h index 7289bbe83..207521dc9 100644 --- a/Classes/Utils/Utils.h +++ b/Classes/Utils/Utils.h @@ -39,7 +39,7 @@ + (UIImage *)resizeImage:(UIImage *)imageToResize newSize:(CGSize)newSize; + (LinphoneAddress *)normalizeSipOrPhoneAddress:(NSString *)addr; -+ (UIAlertController *)networkErrorView; ++ (UIAlertController *)networkErrorView:(NSString *)action; typedef enum { LinphoneDateHistoryList, @@ -74,6 +74,13 @@ typedef enum { @end +@interface UIImageView (ImageWithTint) + +- (void)setImageNamed:(NSString *)name tintColor:(UIColor *)color; +- (void)setImageNamed:(NSString *)name tintColorLetter:(NSString *)letter; + +@end + @interface NSString (linphoneExt) - (NSString *)md5; @@ -111,6 +118,8 @@ typedef enum { - (UIColor *)darkerColor; ++(UIColor *)color:(NSString *)letter; + @end @interface UIImage (ForceDecode) diff --git a/Classes/Utils/Utils.m b/Classes/Utils/Utils.m index 4f6d1249d..b9cbfaaa1 100644 --- a/Classes/Utils/Utils.m +++ b/Classes/Utils/Utils.m @@ -516,12 +516,10 @@ return res; } -+ (UIAlertController *)networkErrorView { ++ (UIAlertController *)networkErrorView:(NSString *)action { UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Network Error", nil) - message:NSLocalizedString(@"There is no network connection available, " - @"enable WIFI or WWAN prior to place a call", - nil) + message:NSLocalizedString([@"There is no network connection available, enable WIFI or WWAN prior to " stringByAppendingString:action],nil) preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) @@ -577,6 +575,23 @@ @end + + +@implementation UIImageView (ImageWithTint) + +- (void)setImageNamed:(NSString *)name tintColor:(UIColor *)color { + self.image = [[UIImage imageNamed:name] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + self.tintColor = color; +} + +- (void)setImageNamed:(NSString *)name tintColorLetter:(NSString *)letter { + UIColor *color = [UIColor color:letter]; + self.image = [[UIImage imageNamed:name] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + self.tintColor = color; +} + +@end + @implementation NSString (md5) - (NSString *)md5 { @@ -806,6 +821,19 @@ return [self lumColor:0.75]; } + +static NSMutableDictionary *letterColors = nil; + ++(UIColor *)color:(NSString *)letter { + if (letterColors == nil) + letterColors = [[NSMutableDictionary alloc] init]; + if (![letterColors objectForKey:letter]) { + UIImage *colorImage = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/color_%@.png",[[NSBundle mainBundle] bundlePath],letter]]; + [letterColors setObject:[UIColor colorWithPatternImage:colorImage] forKey:letter]; + } + return [letterColors objectForKey:letter]; +} + @end @implementation UIImage (ForceDecode) diff --git a/Podfile b/Podfile index 18c1cc72e..d6a747f18 100644 --- a/Podfile +++ b/Podfile @@ -5,7 +5,7 @@ source "https://github.com/CocoaPods/Specs.git" def all_pods if ENV['PODFILE_PATH'].nil? - pod 'linphone-sdk', '~> 5.0.16' + pod 'linphone-sdk', '~> 5.1.0-alpha.75+d4a0bd2' else pod 'linphone-sdk', :path => ENV['PODFILE_PATH'] # local sdk end diff --git a/Resources/en.lproj/Localizable.strings b/Resources/en.lproj/Localizable.strings index a69217772..76f33d13e 100644 Binary files a/Resources/en.lproj/Localizable.strings and b/Resources/en.lproj/Localizable.strings differ diff --git a/Resources/fr.lproj/Localizable.strings b/Resources/fr.lproj/Localizable.strings index b119be4b7..42321344b 100644 Binary files a/Resources/fr.lproj/Localizable.strings and b/Resources/fr.lproj/Localizable.strings differ diff --git a/Resources/images/cancel_forward.png b/Resources/images/cancel_forward.png new file mode 100644 index 000000000..8043430fe Binary files /dev/null and b/Resources/images/cancel_forward.png differ diff --git a/Resources/images/file_audio_default.png b/Resources/images/file_audio_default.png new file mode 100644 index 000000000..ce5af4fd5 Binary files /dev/null and b/Resources/images/file_audio_default.png differ diff --git a/Resources/images/file_default.png b/Resources/images/file_default.png new file mode 100644 index 000000000..f660964fb Binary files /dev/null and b/Resources/images/file_default.png differ diff --git a/Resources/images/file_pdf_default.png b/Resources/images/file_pdf_default.png new file mode 100644 index 000000000..3a6a30c94 Binary files /dev/null and b/Resources/images/file_pdf_default.png differ diff --git a/Resources/images/file_picture_default.png b/Resources/images/file_picture_default.png new file mode 100644 index 000000000..26f2c8d0c Binary files /dev/null and b/Resources/images/file_picture_default.png differ diff --git a/Resources/images/file_video_default.png b/Resources/images/file_video_default.png new file mode 100644 index 000000000..9a3d1a301 Binary files /dev/null and b/Resources/images/file_video_default.png differ diff --git a/Resources/images/file_voice_default.png b/Resources/images/file_voice_default.png new file mode 100644 index 000000000..a492d31a5 Binary files /dev/null and b/Resources/images/file_voice_default.png differ diff --git a/Resources/images/forward_message_default.png b/Resources/images/forward_message_default.png new file mode 100644 index 000000000..b51542ec8 Binary files /dev/null and b/Resources/images/forward_message_default.png differ diff --git a/Resources/images/menu_copy_text_default.png b/Resources/images/menu_copy_text_default.png new file mode 100644 index 000000000..ccdef4e4b Binary files /dev/null and b/Resources/images/menu_copy_text_default.png differ diff --git a/Resources/images/menu_delete.png b/Resources/images/menu_delete.png new file mode 100644 index 000000000..947c0abf4 Binary files /dev/null and b/Resources/images/menu_delete.png differ diff --git a/Resources/images/menu_forward_default.png b/Resources/images/menu_forward_default.png new file mode 100644 index 000000000..910450bf9 Binary files /dev/null and b/Resources/images/menu_forward_default.png differ diff --git a/Resources/images/menu_info.png b/Resources/images/menu_info.png new file mode 100644 index 000000000..d45da33d5 Binary files /dev/null and b/Resources/images/menu_info.png differ diff --git a/Resources/images/menu_reply_default.png b/Resources/images/menu_reply_default.png new file mode 100644 index 000000000..8aa5b5e1e Binary files /dev/null and b/Resources/images/menu_reply_default.png differ diff --git a/Resources/images/menu_resend_default.png b/Resources/images/menu_resend_default.png new file mode 100644 index 000000000..5f5ab4ddd Binary files /dev/null and b/Resources/images/menu_resend_default.png differ diff --git a/Resources/images/reply_cancel.png b/Resources/images/reply_cancel.png new file mode 100644 index 000000000..8156bd629 Binary files /dev/null and b/Resources/images/reply_cancel.png differ diff --git a/Resources/images/vr_off.png b/Resources/images/vr_off.png new file mode 100644 index 000000000..5f319e5c8 Binary files /dev/null and b/Resources/images/vr_off.png differ diff --git a/Resources/images/vr_on.png b/Resources/images/vr_on.png new file mode 100644 index 000000000..9fd24b67c Binary files /dev/null and b/Resources/images/vr_on.png differ diff --git a/Resources/images/vr_pause.png b/Resources/images/vr_pause.png new file mode 100644 index 000000000..af2d89acc Binary files /dev/null and b/Resources/images/vr_pause.png differ diff --git a/Resources/images/vr_play.png b/Resources/images/vr_play.png new file mode 100644 index 000000000..6de2686f0 Binary files /dev/null and b/Resources/images/vr_play.png differ diff --git a/Resources/images/vr_stop.png b/Resources/images/vr_stop.png new file mode 100644 index 000000000..6574a7d56 Binary files /dev/null and b/Resources/images/vr_stop.png differ diff --git a/Resources/images/vr_wave.png b/Resources/images/vr_wave.png new file mode 100644 index 000000000..26d7f1be7 Binary files /dev/null and b/Resources/images/vr_wave.png differ diff --git a/linphone.xcodeproj/project.pbxproj b/linphone.xcodeproj/project.pbxproj index 9d049a72a..3fbac849c 100644 --- a/linphone.xcodeproj/project.pbxproj +++ b/linphone.xcodeproj/project.pbxproj @@ -109,7 +109,7 @@ 61AE364F20C00B370089D9D3 /* ShareViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 61AE364E20C00B370089D9D3 /* ShareViewController.m */; }; 61AE365220C00B370089D9D3 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 61AE365020C00B370089D9D3 /* MainInterface.storyboard */; }; 61AE365620C00B370089D9D3 /* linphoneExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 61AE364B20C00B370089D9D3 /* linphoneExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 61AEBEA321906AFC00F35E7F /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + 61AEBEA321906AFC00F35E7F /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; 61AEBEBD2191990A00F35E7F /* DevicesListView.m in Sources */ = {isa = PBXBuildFile; fileRef = 61AEBEBC2191990A00F35E7F /* DevicesListView.m */; }; 61AEBEBF2191991F00F35E7F /* DevicesListView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 61AEBEBE2191991F00F35E7F /* DevicesListView.xib */; }; 61AEBEC62191E47500F35E7F /* chevron_list_close.png in Resources */ = {isa = PBXBuildFile; fileRef = 61AEBEC52191E47500F35E7F /* chevron_list_close.png */; }; @@ -622,7 +622,7 @@ 63E27A321C4FECD000D332AE /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 63E27A311C4FECD000D332AE /* LaunchScreen.xib */; }; 63E27A521C50EDB000D332AE /* hold.mkv in Resources */ = {isa = PBXBuildFile; fileRef = 63E27A511C50EB2700D332AE /* hold.mkv */; }; 63E59A3F1ADE70D900646FB3 /* InAppProductsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 63E59A3E1ADE70D900646FB3 /* InAppProductsManager.m */; }; - 63E802DB1C625AEF000D5509 /* (null) in Resources */ = {isa = PBXBuildFile; }; + 63E802DB1C625AEF000D5509 /* BuildFile in Resources */ = {isa = PBXBuildFile; }; 63EC8D391D7438660066547B /* AssistantLinkView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 63EC8D3B1D7438660066547B /* AssistantLinkView.xib */; }; 63F1DF441BCE618E00EDED90 /* UIAddressTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F1DF431BCE618E00EDED90 /* UIAddressTextField.m */; }; 63F1DF4B1BCE983200EDED90 /* CallConferenceTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F1DF4A1BCE983200EDED90 /* CallConferenceTableView.m */; }; @@ -675,11 +675,35 @@ C61B1BF22667D075001A4E4A /* menu_security_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C61B1BF12667D075001A4E4A /* menu_security_default.png */; }; C61B1BF42667D202001A4E4A /* more_menu_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C61B1BF32667D202001A4E4A /* more_menu_default.png */; }; C61B1BF72667EC6B001A4E4A /* ephemeral_messages_color_A.png in Resources */ = {isa = PBXBuildFile; fileRef = C61B1BF62667EC6B001A4E4A /* ephemeral_messages_color_A.png */; }; + C622E3EF26A81290004F5434 /* vr_stop.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3E926A8128F004F5434 /* vr_stop.png */; }; + C622E3F026A81290004F5434 /* vr_wave.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3EA26A8128F004F5434 /* vr_wave.png */; }; + C622E3F126A81290004F5434 /* vr_on.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3EB26A8128F004F5434 /* vr_on.png */; }; + C622E3F226A81290004F5434 /* vr_off.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3EC26A8128F004F5434 /* vr_off.png */; }; + C622E3F326A81290004F5434 /* vr_pause.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3ED26A8128F004F5434 /* vr_pause.png */; }; + C622E3F426A81290004F5434 /* vr_play.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3EE26A81290004F5434 /* vr_play.png */; }; C64A854E2667B67200252AD2 /* EphemeralSettingsView.m in Sources */ = {isa = PBXBuildFile; fileRef = C64A854D2667B67200252AD2 /* EphemeralSettingsView.m */; }; C64A85502667B67A00252AD2 /* EphemeralSettingsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C64A854F2667B67A00252AD2 /* EphemeralSettingsView.xib */; }; C64A85522667B74100252AD2 /* ephemeral_messages_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C64A85512667B74100252AD2 /* ephemeral_messages_default.png */; }; C666756F264C925800A0273C /* VFSUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DA657B261C950C0020CB43 /* VFSUtil.swift */; }; C6667571264C925B00A0273C /* VFSUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DA657B261C950C0020CB43 /* VFSUtil.swift */; }; + C66B03BB26E8EB1A009B5EDC /* UIChatReplyBubbleView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C66B03BD26E8EB1A009B5EDC /* UIChatReplyBubbleView.xib */; }; + C66B040A26EFDA55009B5EDC /* reply_cancel.png in Resources */ = {isa = PBXBuildFile; fileRef = C66B040926EFDA54009B5EDC /* reply_cancel.png */; }; + C66B040E26F095D1009B5EDC /* cancel_forward.png in Resources */ = {isa = PBXBuildFile; fileRef = C66B040D26F095CE009B5EDC /* cancel_forward.png */; }; + C6A1BB3526E8815400540D50 /* menu_info.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB3126E8815300540D50 /* menu_info.png */; }; + C6A1BB3626E8815400540D50 /* menu_forward_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB3226E8815400540D50 /* menu_forward_default.png */; }; + C6A1BB3726E8815400540D50 /* menu_copy_text_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB3326E8815400540D50 /* menu_copy_text_default.png */; }; + C6A1BB3826E8815400540D50 /* menu_reply_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB3426E8815400540D50 /* menu_reply_default.png */; }; + C6A1BB3A26E881E100540D50 /* menu_delete.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB3926E881E100540D50 /* menu_delete.png */; }; + C6A1BB3E26E882D000540D50 /* UIChatReplyBubbleView.m in Sources */ = {isa = PBXBuildFile; fileRef = C6A1BB3C26E882D000540D50 /* UIChatReplyBubbleView.m */; }; + C6A1BB4126E889AD00540D50 /* forward_message_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB4026E889AD00540D50 /* forward_message_default.png */; }; + C6A1BB4326E88F7C00540D50 /* menu_resend_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB4226E88F7C00540D50 /* menu_resend_default.png */; }; + C6A1BB4526E890BD00540D50 /* file_voice_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB4426E890BD00540D50 /* file_voice_default.png */; }; + C6B4444226AAD0980076C517 /* file_video_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6B4443D26AAD0970076C517 /* file_video_default.png */; }; + C6B4444326AAD0980076C517 /* file_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6B4443E26AAD0970076C517 /* file_default.png */; }; + C6B4444426AAD0980076C517 /* file_picture_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6B4443F26AAD0970076C517 /* file_picture_default.png */; }; + C6B4444526AAD0980076C517 /* file_audio_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6B4444026AAD0970076C517 /* file_audio_default.png */; }; + C6B4444626AAD0980076C517 /* file_pdf_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6B4444126AAD0970076C517 /* file_pdf_default.png */; }; + C6B4444826AADA530076C517 /* SwiftUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B4444726AADA530076C517 /* SwiftUtil.swift */; }; C6DA657C261C950C0020CB43 /* VFSUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DA657B261C950C0020CB43 /* VFSUtil.swift */; }; C90FAA7915AF54E6002091CB /* HistoryDetailsView.m in Sources */ = {isa = PBXBuildFile; fileRef = C90FAA7715AF54E6002091CB /* HistoryDetailsView.m */; }; CF15F21E20E4F9A3008B1DE6 /* UIImageViewDeletable.m in Sources */ = {isa = PBXBuildFile; fileRef = CF15F21C20E4F9A3008B1DE6 /* UIImageViewDeletable.m */; }; @@ -1714,10 +1738,37 @@ C61B1BF12667D075001A4E4A /* menu_security_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_security_default.png; sourceTree = ""; }; C61B1BF32667D202001A4E4A /* more_menu_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = more_menu_default.png; sourceTree = ""; }; C61B1BF62667EC6B001A4E4A /* ephemeral_messages_color_A.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ephemeral_messages_color_A.png; sourceTree = ""; }; + C622E3E926A8128F004F5434 /* vr_stop.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_stop.png; sourceTree = ""; }; + C622E3EA26A8128F004F5434 /* vr_wave.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_wave.png; sourceTree = ""; }; + C622E3EB26A8128F004F5434 /* vr_on.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_on.png; sourceTree = ""; }; + C622E3EC26A8128F004F5434 /* vr_off.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_off.png; sourceTree = ""; }; + C622E3ED26A8128F004F5434 /* vr_pause.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_pause.png; sourceTree = ""; }; + C622E3EE26A81290004F5434 /* vr_play.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_play.png; sourceTree = ""; }; C64A854C2667B66900252AD2 /* EphemeralSettingsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EphemeralSettingsView.h; sourceTree = ""; }; C64A854D2667B67200252AD2 /* EphemeralSettingsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EphemeralSettingsView.m; sourceTree = ""; }; C64A854F2667B67A00252AD2 /* EphemeralSettingsView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EphemeralSettingsView.xib; sourceTree = ""; }; C64A85512667B74100252AD2 /* ephemeral_messages_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ephemeral_messages_default.png; sourceTree = ""; }; + C66B03BC26E8EB1A009B5EDC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UIChatReplyBubbleView.xib; sourceTree = ""; }; + C66B03C126E8EB82009B5EDC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/UIChatReplyBubbleView.strings; sourceTree = ""; }; + C66B03C326E8EB87009B5EDC /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/UIChatReplyBubbleView.strings; sourceTree = ""; }; + C66B040926EFDA54009B5EDC /* reply_cancel.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = reply_cancel.png; sourceTree = ""; }; + C66B040D26F095CE009B5EDC /* cancel_forward.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cancel_forward.png; sourceTree = ""; }; + C6A1BB3126E8815300540D50 /* menu_info.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_info.png; sourceTree = ""; }; + C6A1BB3226E8815400540D50 /* menu_forward_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_forward_default.png; sourceTree = ""; }; + C6A1BB3326E8815400540D50 /* menu_copy_text_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_copy_text_default.png; sourceTree = ""; }; + C6A1BB3426E8815400540D50 /* menu_reply_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_reply_default.png; sourceTree = ""; }; + C6A1BB3926E881E100540D50 /* menu_delete.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_delete.png; sourceTree = ""; }; + C6A1BB3B26E882D000540D50 /* UIChatReplyBubbleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIChatReplyBubbleView.h; sourceTree = ""; }; + C6A1BB3C26E882D000540D50 /* UIChatReplyBubbleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIChatReplyBubbleView.m; sourceTree = ""; }; + C6A1BB4026E889AD00540D50 /* forward_message_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = forward_message_default.png; sourceTree = ""; }; + C6A1BB4226E88F7C00540D50 /* menu_resend_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_resend_default.png; sourceTree = ""; }; + C6A1BB4426E890BD00540D50 /* file_voice_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = file_voice_default.png; sourceTree = ""; }; + C6B4443D26AAD0970076C517 /* file_video_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = file_video_default.png; sourceTree = ""; }; + C6B4443E26AAD0970076C517 /* file_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = file_default.png; sourceTree = ""; }; + C6B4443F26AAD0970076C517 /* file_picture_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = file_picture_default.png; sourceTree = ""; }; + C6B4444026AAD0970076C517 /* file_audio_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = file_audio_default.png; sourceTree = ""; }; + C6B4444126AAD0970076C517 /* file_pdf_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = file_pdf_default.png; sourceTree = ""; }; + C6B4444726AADA530076C517 /* SwiftUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUtil.swift; sourceTree = ""; }; C6DA657B261C950C0020CB43 /* VFSUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VFSUtil.swift; sourceTree = ""; }; C90FAA7615AF54E6002091CB /* HistoryDetailsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HistoryDetailsView.h; sourceTree = ""; }; C90FAA7715AF54E6002091CB /* HistoryDetailsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HistoryDetailsView.m; sourceTree = ""; }; @@ -1951,7 +2002,7 @@ files = ( 61DD7E1F2372E88F001BDD01 /* CoreLocation.framework in Frameworks */, 6180D6FE21EE41A800AD9CB6 /* QuickLook.framework in Frameworks */, - 61AEBEA321906AFC00F35E7F /* (null) in Frameworks */, + 61AEBEA321906AFC00F35E7F /* BuildFile in Frameworks */, D37DC7181594AF3400B2A5EB /* MessageUI.framework in Frameworks */, 61F1997520C6B1D5006B069A /* AVKit.framework in Frameworks */, 249660951FD6A35F001D55AA /* Photos.framework in Frameworks */, @@ -2166,6 +2217,9 @@ 2214EB7012F84668002A5394 /* LinphoneUI */ = { isa = PBXGroup; children = ( + C6A1BB3B26E882D000540D50 /* UIChatReplyBubbleView.h */, + C6A1BB3C26E882D000540D50 /* UIChatReplyBubbleView.m */, + C66B03BD26E8EB1A009B5EDC /* UIChatReplyBubbleView.xib */, 63F1DF421BCE618E00EDED90 /* UIAddressTextField.h */, 63F1DF431BCE618E00EDED90 /* UIAddressTextField.m */, 63C441C11BBC23ED0053DC5E /* UIAssistantTextField.h */, @@ -2285,7 +2339,7 @@ path = LinphoneUI; sourceTree = ""; }; - 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { + 29B97314FDCFA39411CA2CEA = { isa = PBXGroup; children = ( 8C23BCB71D82AAC3005F19BB /* linphone.entitlements */, @@ -2447,6 +2501,27 @@ 633FEBE11D3CD5570014B822 /* images */ = { isa = PBXGroup; children = ( + C66B040D26F095CE009B5EDC /* cancel_forward.png */, + C66B040926EFDA54009B5EDC /* reply_cancel.png */, + C6A1BB4426E890BD00540D50 /* file_voice_default.png */, + C6A1BB4226E88F7C00540D50 /* menu_resend_default.png */, + C6A1BB4026E889AD00540D50 /* forward_message_default.png */, + C6A1BB3926E881E100540D50 /* menu_delete.png */, + C6A1BB3326E8815400540D50 /* menu_copy_text_default.png */, + C6A1BB3226E8815400540D50 /* menu_forward_default.png */, + C6A1BB3126E8815300540D50 /* menu_info.png */, + C6A1BB3426E8815400540D50 /* menu_reply_default.png */, + C6B4444026AAD0970076C517 /* file_audio_default.png */, + C6B4443E26AAD0970076C517 /* file_default.png */, + C6B4444126AAD0970076C517 /* file_pdf_default.png */, + C6B4443F26AAD0970076C517 /* file_picture_default.png */, + C6B4443D26AAD0970076C517 /* file_video_default.png */, + C622E3EC26A8128F004F5434 /* vr_off.png */, + C622E3EB26A8128F004F5434 /* vr_on.png */, + C622E3ED26A8128F004F5434 /* vr_pause.png */, + C622E3EE26A81290004F5434 /* vr_play.png */, + C622E3E926A8128F004F5434 /* vr_stop.png */, + C622E3EA26A8128F004F5434 /* vr_wave.png */, C61B1BF62667EC6B001A4E4A /* ephemeral_messages_color_A.png */, C61B1BF32667D202001A4E4A /* more_menu_default.png */, C61B1BF12667D075001A4E4A /* menu_security_default.png */, @@ -3043,6 +3118,7 @@ 63423C091C4501D000D9A050 /* Contact.m */, 8C1B67081E6718BC001EA2FE /* AudioHelper.h */, 8C1B67051E671826001EA2FE /* AudioHelper.m */, + C6B4444726AADA530076C517 /* SwiftUtil.swift */, ); name = Utils; sourceTree = ""; @@ -3357,7 +3433,7 @@ fr, hu, ); - mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; + mainGroup = 29B97314FDCFA39411CA2CEA; productRefGroup = 19C28FACFE9D520D11CA2CBB /* Products */; projectDirPath = ""; projectRoot = ""; @@ -3378,6 +3454,7 @@ 633FEF3F1D3CD55A0014B822 /* security_pending@2x.png in Resources */, 24BFAAA4209B0630004F47A7 /* linphone_logo.png in Resources */, 633FEDC41D3CD5590014B822 /* call_hangup_disabled.png in Resources */, + C66B03BB26E8EB1A009B5EDC /* UIChatReplyBubbleView.xib in Resources */, 633FEDA81D3CD5590014B822 /* backspace_default.png in Resources */, 636316D11A1DEBCB0009B839 /* AboutView.xib in Resources */, 8CBD7BA620B6B82400E5DCC0 /* UIChatConversationInfoTableViewCell.xib in Resources */, @@ -3408,6 +3485,7 @@ 615A2817217F280C0060F920 /* chat_list_indicator.png in Resources */, 633FEEFF1D3CD55A0014B822 /* options_add_call_disabled@2x.png in Resources */, 633FEF091D3CD55A0014B822 /* options_start_conference_disabled@2x.png in Resources */, + C622E3F326A81290004F5434 /* vr_pause.png in Resources */, 633FEE051D3CD5590014B822 /* cancel_edit_disabled@2x.png in Resources */, 633FEE5F1D3CD5590014B822 /* edit_list_default@2x.png in Resources */, 633FEEB61D3CD55A0014B822 /* numpad_3_over@2x.png in Resources */, @@ -3418,7 +3496,7 @@ 633FEED41D3CD55A0014B822 /* numpad_7_default@2x.png in Resources */, 633FEEE01D3CD55A0014B822 /* numpad_8_over~ipad@2x.png in Resources */, 633FEDDC1D3CD5590014B822 /* call_start_body_disabled~ipad.png in Resources */, - 63E802DB1C625AEF000D5509 /* (null) in Resources */, + 63E802DB1C625AEF000D5509 /* BuildFile in Resources */, 633FEE2E1D3CD5590014B822 /* color_F.png in Resources */, 633FEDC51D3CD5590014B822 /* call_hangup_disabled@2x.png in Resources */, 633FEEDF1D3CD55A0014B822 /* numpad_8_over~ipad.png in Resources */, @@ -3443,6 +3521,7 @@ 633FEF331D3CD55A0014B822 /* route_speaker_selected@2x.png in Resources */, 633FEE6C1D3CD5590014B822 /* footer_dialer_disabled.png in Resources */, 633FEF231D3CD55A0014B822 /* route_bluetooth_default@2x.png in Resources */, + C6A1BB4526E890BD00540D50 /* file_voice_default.png in Resources */, 633FED9C1D3CD5590014B822 /* add_field_default.png in Resources */, 633FEE411D3CD5590014B822 /* contacts_all_selected@2x.png in Resources */, D38187F815FE355D00C3EDCA /* TabBarView.xib in Resources */, @@ -3453,6 +3532,7 @@ 633FEDD91D3CD5590014B822 /* call_start_body_default~ipad@2x.png in Resources */, 633FEE401D3CD5590014B822 /* contacts_all_selected.png in Resources */, 633FEE0C1D3CD5590014B822 /* chat_attachment_disabled.png in Resources */, + C622E3EF26A81290004F5434 /* vr_stop.png in Resources */, 633FEF001D3CD55A0014B822 /* options_default.png in Resources */, CF15F21F20E4F9A3008B1DE6 /* UIImageViewDeletable.xib in Resources */, 633FEE951D3CD55A0014B822 /* micro_default@2x.png in Resources */, @@ -3478,6 +3558,7 @@ 633FEE7A1D3CD5590014B822 /* history_missed_default.png in Resources */, 633FEF121D3CD55A0014B822 /* pause_big_over_selected.png in Resources */, 633FED9D1D3CD5590014B822 /* add_field_default@2x.png in Resources */, + C622E3F426A81290004F5434 /* vr_play.png in Resources */, 639E9CB01C0DB83000019A75 /* SideMenuView.xib in Resources */, 633FEDBB1D3CD5590014B822 /* call_audio_start_default@2x.png in Resources */, 633FEF1A1D3CD55A0014B822 /* presence_away.png in Resources */, @@ -3505,6 +3586,7 @@ 615A283E2180A2560060F920 /* invite_linphone.png in Resources */, 633FEF281D3CD55A0014B822 /* route_earpiece_default.png in Resources */, 633FEE4F1D3CD5590014B822 /* delete_field_over@2x.png in Resources */, + C622E3F226A81290004F5434 /* vr_off.png in Resources */, 633FEE531D3CD5590014B822 /* dialer_alt_back@2x.png in Resources */, 633FEE3E1D3CD5590014B822 /* contacts_all_disabled.png in Resources */, 633FEEF31D3CD55A0014B822 /* numpad_over_background.png in Resources */, @@ -3514,6 +3596,7 @@ 633FEEED1D3CD55A0014B822 /* numpad_hash_over.png in Resources */, 633FEE1F1D3CD5590014B822 /* chat_start_body_disabled@2x.png in Resources */, 633FEEF81D3CD55A0014B822 /* numpad_star_over~ipad.png in Resources */, + C6A1BB3826E8815400540D50 /* menu_reply_default.png in Resources */, 633FEF301D3CD55A0014B822 /* route_speaker_disabled.png in Resources */, 639CEAFD1A1DF4D9004DE38F /* StatusBarView.xib in Resources */, 633FEDE91D3CD5590014B822 /* call_status_missed~ipad@2x.png in Resources */, @@ -3547,6 +3630,7 @@ 633FEEC81D3CD55A0014B822 /* numpad_5_over~ipad@2x.png in Resources */, 61586B91217A175D0038AC45 /* menu_recordings.png in Resources */, 633FEF1B1D3CD55A0014B822 /* presence_away@2x.png in Resources */, + C6A1BB3A26E881E100540D50 /* menu_delete.png in Resources */, 633FEE281D3CD5590014B822 /* checkbox_unchecked.png in Resources */, 633FEE9D1D3CD55A0014B822 /* numpad_0_over.png in Resources */, 633FEEC21D3CD55A0014B822 /* numpad_4~ipad@2x.png in Resources */, @@ -3563,6 +3647,7 @@ 633FEEC01D3CD55A0014B822 /* numpad_4_over~ipad@2x.png in Resources */, 61586B8B217A17320038AC45 /* menu_link_account@2x.png in Resources */, 63CDC4661C3BDE370085F529 /* shortring.caf in Resources */, + C6A1BB4126E889AD00540D50 /* forward_message_default.png in Resources */, 633FEDD51D3CD5590014B822 /* call_quality_indicator_4@2x.png in Resources */, 633FEDE71D3CD5590014B822 /* call_status_missed@2x.png in Resources */, 615A2821217F6FBF0060F920 /* security_alert_indicator@2x.png in Resources */, @@ -3602,6 +3687,7 @@ 633FEDFD1D3CD5590014B822 /* camera_switch_default@2x.png in Resources */, 633FEEC51D3CD55A0014B822 /* numpad_5_over.png in Resources */, 633FEE721D3CD5590014B822 /* history_all_default.png in Resources */, + C6B4444326AAD0980076C517 /* file_default.png in Resources */, 615A283C2180789C0060F920 /* security_toogle_button@2x.png in Resources */, 633FEF0A1D3CD55A0014B822 /* options_transfer_call_default.png in Resources */, 633FEDA51D3CD5590014B822 /* back_default@2x.png in Resources */, @@ -3627,6 +3713,7 @@ 63E27A321C4FECD000D332AE /* LaunchScreen.xib in Resources */, 633FEED11D3CD55A0014B822 /* numpad_6~ipad.png in Resources */, CF7602E82108759A00749F76 /* UIRecordingCell.xib in Resources */, + C6B4444626AAD0980076C517 /* file_pdf_default.png in Resources */, 633FEED21D3CD55A0014B822 /* numpad_6~ipad@2x.png in Resources */, 633FEDCD1D3CD5590014B822 /* call_quality_indicator_0@2x.png in Resources */, 636316D41A1DEC650009B839 /* SettingsView.xib in Resources */, @@ -3691,6 +3778,7 @@ 24BFAAA5209B0630004F47A7 /* contacts_sip_default.png in Resources */, 633FEF441D3CD55A0014B822 /* speaker_default.png in Resources */, 639CEB031A1DF4EB004DE38F /* UICompositeView.xib in Resources */, + C6A1BB4326E88F7C00540D50 /* menu_resend_default.png in Resources */, 633FEF3A1D3CD55A0014B822 /* security_ko.png in Resources */, 615A283A2180788E0060F920 /* security_toogle_button.png in Resources */, 633FEDA01D3CD5590014B822 /* avatar.png in Resources */, @@ -3722,6 +3810,7 @@ 63CDC45E1C3BDE370085F529 /* msg.caf in Resources */, 633FEE6D1D3CD5590014B822 /* footer_dialer_disabled@2x.png in Resources */, 633FEF171D3CD55A0014B822 /* pause_small_disabled@2x.png in Resources */, + C6A1BB3626E8815400540D50 /* menu_forward_default.png in Resources */, D38187DD15FE348A00C3EDCA /* AssistantView.xib in Resources */, 633FEDA61D3CD5590014B822 /* back_disabled.png in Resources */, 633FEED61D3CD55A0014B822 /* numpad_7_over@2x.png in Resources */, @@ -3740,7 +3829,9 @@ 633FEE191D3CD5590014B822 /* chat_send_over@2x.png in Resources */, 633FEF181D3CD55A0014B822 /* pause_small_over_selected.png in Resources */, 633FEE001D3CD5590014B822 /* camera_switch_over.png in Resources */, + C622E3F126A81290004F5434 /* vr_on.png in Resources */, 633FEF401D3CD55A0014B822 /* select_all_default.png in Resources */, + C6B4444426AAD0980076C517 /* file_picture_default.png in Resources */, 633FEDF01D3CD5590014B822 /* call_transfer_disabled.png in Resources */, 633FEE351D3CD5590014B822 /* conference_exit_default@2x.png in Resources */, 633FEECF1D3CD55A0014B822 /* numpad_6_over~ipad.png in Resources */, @@ -3772,6 +3863,7 @@ 633FEEFA1D3CD55A0014B822 /* numpad_star~ipad.png in Resources */, D38187B915FE342200C3EDCA /* ContactDetailsView.xib in Resources */, 633FEE921D3CD55A0014B822 /* menu.png in Resources */, + C622E3F026A81290004F5434 /* vr_wave.png in Resources */, 633FEDE41D3CD5590014B822 /* call_status_incoming~ipad.png in Resources */, 633FEE4C1D3CD5590014B822 /* delete_field_default.png in Resources */, 633FEE391D3CD5590014B822 /* contact_add_default@2x.png in Resources */, @@ -3831,8 +3923,10 @@ 633FEEF71D3CD55A0014B822 /* numpad_star_over@2x.png in Resources */, 633FEEAB1D3CD55A0014B822 /* numpad_2_default.png in Resources */, 633FEE851D3CD5590014B822 /* led_error@2x.png in Resources */, + C6B4444226AAD0980076C517 /* file_video_default.png in Resources */, 633FEDBE1D3CD5590014B822 /* call_back_default.png in Resources */, 633FEF0F1D3CD55A0014B822 /* pause_big_default@2x.png in Resources */, + C66B040E26F095D1009B5EDC /* cancel_forward.png in Resources */, CF7602F6210898CC00749F76 /* rec_on_default@2x.png in Resources */, 633FEF081D3CD55A0014B822 /* options_start_conference_disabled.png in Resources */, 63F1DF511BCE986A00EDED90 /* UICallConferenceCell.xib in Resources */, @@ -3862,6 +3956,7 @@ 633FEE221D3CD5590014B822 /* chat_start_body_over.png in Resources */, 633FEE601D3CD5590014B822 /* edit_list_disabled.png in Resources */, D38187C115FE345B00C3EDCA /* DialerView.xib in Resources */, + C6A1BB3726E8815400540D50 /* menu_copy_text_default.png in Resources */, D37EE10D16035793003608A6 /* ImageView.xib in Resources */, 633FEE9F1D3CD55A0014B822 /* numpad_0_over~ipad.png in Resources */, 633FEED51D3CD55A0014B822 /* numpad_7_over.png in Resources */, @@ -3876,6 +3971,8 @@ 633FEF3B1D3CD55A0014B822 /* security_ko@2x.png in Resources */, 633FEE4A1D3CD5590014B822 /* delete_disabled.png in Resources */, 614D09CE21E74D5400C43EDF /* GoogleService-Info.plist in Resources */, + C6B4444526AAD0980076C517 /* file_audio_default.png in Resources */, + C6A1BB3526E8815400540D50 /* menu_info.png in Resources */, C61B1BF72667EC6B001A4E4A /* ephemeral_messages_color_A.png in Resources */, 633FEF151D3CD55A0014B822 /* pause_small_default@2x.png in Resources */, 633FEEF91D3CD55A0014B822 /* numpad_star_over~ipad@2x.png in Resources */, @@ -3950,6 +4047,7 @@ 633FEDDD1D3CD5590014B822 /* call_start_body_disabled~ipad@2x.png in Resources */, 633FEEBD1D3CD55A0014B822 /* numpad_4_over.png in Resources */, 8CA70AD41F9E285C00A3D2EB /* chat_group_add@2x.png in Resources */, + C66B040A26EFDA55009B5EDC /* reply_cancel.png in Resources */, 633FEEF11D3CD55A0014B822 /* numpad_hash~ipad.png in Resources */, 633FEE781D3CD5590014B822 /* history_chat_indicator.png in Resources */, 633FEF431D3CD55A0014B822 /* select_all_disabled@2x.png in Resources */, @@ -4195,6 +4293,7 @@ 6135761C240E81BB005304D4 /* UIInterfaceStyleButton.m in Sources */, 8CD99A3C2090B9FA008A7CDA /* ChatConversationImdnView.m in Sources */, 1D3623260D0F684500981E51 /* LinphoneAppDelegate.m in Sources */, + C6B4444826AADA530076C517 /* SwiftUtil.swift in Sources */, 614C087A23D1A37400217F80 /* CallManager.swift in Sources */, CF15F21E20E4F9A3008B1DE6 /* UIImageViewDeletable.m in Sources */, 22F2508E107141E100AC9B3F /* DialerView.m in Sources */, @@ -4227,6 +4326,7 @@ CF7602D7210867E800749F76 /* RecordingsListView.m in Sources */, 63F1DF4B1BCE983200EDED90 /* CallConferenceTableView.m in Sources */, D3F83F8E15822ABE00336684 /* PhoneMainView.m in Sources */, + C6A1BB3E26E882D000540D50 /* UIChatReplyBubbleView.m in Sources */, 6377AC801BDE4069007F7625 /* UIBackToCallButton.m in Sources */, 6308F9C51BF0DD6600D1234B /* XMLRPCHelper.m in Sources */, D3ED3E871586291E006C0DE4 /* TabBarView.m in Sources */, @@ -4716,6 +4816,16 @@ name = UIChatCreateCollectionViewCell.xib; sourceTree = ""; }; + C66B03BD26E8EB1A009B5EDC /* UIChatReplyBubbleView.xib */ = { + isa = PBXVariantGroup; + children = ( + C66B03BC26E8EB1A009B5EDC /* Base */, + C66B03C126E8EB82009B5EDC /* en */, + C66B03C326E8EB87009B5EDC /* fr */, + ); + name = UIChatReplyBubbleView.xib; + sourceTree = ""; + }; D37EE11016035793003608A6 /* ImageView.xib */ = { isa = PBXVariantGroup; children = ( @@ -4949,7 +5059,7 @@ "-DENABLE_QRCODE=TRUE", "-DENABLE_SMS_INVITE=TRUE", "$(inherited)", - "-DLINPHONE_SDK_VERSION=\\\"5.0.0-alpha.109+40dd0cf\\\"", + "-DLINPHONE_SDK_VERSION=\\\"5.1.0-alpha.75+d4a0bd2\\\"", ); OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone; @@ -5072,7 +5182,7 @@ "-DENABLE_QRCODE=TRUE", "-DENABLE_SMS_INVITE=TRUE", "$(inherited)", - "-DLINPHONE_SDK_VERSION=\\\"5.0.0-alpha.109+40dd0cf\\\"", + "-DLINPHONE_SDK_VERSION=\\\"5.1.0-alpha.75+d4a0bd2\\\"", ); OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone; @@ -5194,7 +5304,7 @@ "-DENABLE_QRCODE=TRUE", "-DENABLE_SMS_INVITE=TRUE", "$(inherited)", - "-DLINPHONE_SDK_VERSION=\\\"5.0.0-alpha.109+40dd0cf\\\"", + "-DLINPHONE_SDK_VERSION=\\\"5.1.0-alpha.75+d4a0bd2\\\"", ); OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone; @@ -5315,7 +5425,7 @@ "-DENABLE_QRCODE=TRUE", "-DENABLE_SMS_INVITE=TRUE", "$(inherited)", - "-DLINPHONE_SDK_VERSION=\\\"5.0.0-alpha.109+40dd0cf\\\"", + "-DLINPHONE_SDK_VERSION=\\\"5.1.0-alpha.75+d4a0bd2\\\"", ); OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone;