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/ChatConversationView.xib b/Classes/Base.lproj/ChatConversationView.xib index e321384a1..62255dabe 100644 --- a/Classes/Base.lproj/ChatConversationView.xib +++ b/Classes/Base.lproj/ChatConversationView.xib @@ -34,9 +34,18 @@ + + + + + + + + + @@ -209,7 +218,7 @@ - + @@ -221,7 +230,7 @@ + @@ -551,8 +625,21 @@ + - + @@ -637,10 +724,14 @@ - + + + + + diff --git a/Classes/CallManager.swift b/Classes/CallManager.swift index 68c0869eb..a9546176a 100644 --- a/Classes/CallManager.swift +++ b/Classes/CallManager.swift @@ -639,6 +639,25 @@ import AVFoundation AnyHashable("message"): message ]) } + + @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 + } } diff --git a/Classes/ChatConversationTableView.h b/Classes/ChatConversationTableView.h index 3fb5e83be..1a3f783d7 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)resendMultiFiles:(FileContext *)newFileContext message:(NSString *)message voiceContent:(LinphoneContent *)voiceContent; +- (BOOL)resendFile:(NSData *)data withName:(NSString *)name type:(NSString *)type key:(NSString *)key message:(NSString *)message voiceContent:(LinphoneContent *)voiceContent; - (BOOL)startFileUpload:(NSData *)data withName:(NSString *)name; -- (void)resendChat:(NSString *)message withExternalUrl:(NSString *)url; +- (void)resendChat:(NSString *)message withExternalUrl:(NSString *)url voiceContent:(LinphoneContent *)voiceContent; - (void)tableViewIsScrolling; @end diff --git a/Classes/ChatConversationView.h b/Classes/ChatConversationView.h index 06bb89c17..1a8d77309 100644 --- a/Classes/ChatConversationView.h +++ b/Classes/ChatConversationView.h @@ -92,6 +92,28 @@ @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 preservePendingRecording; + + + (void)markAsRead:(LinphoneChatRoom *)chatRoom; + (void)autoDownload:(LinphoneChatMessage *)message; +(NSString *)getKeyFromFileType:(NSString *)fileType fileName:(NSString *)name; @@ -123,4 +145,8 @@ - (NSURL *)getICloudFileUrl:(NSString *)name; - (void)removeCallBacks; +-(void) startSharedPlayer:(const char *)path; +-(void) stopSharedPlayer; +-(BOOL) sharedPlayedIsPlaying:(const char *)path; + @end diff --git a/Classes/ChatConversationView.m b/Classes/ChatConversationView.m index bfdca00f3..05ec3b14c 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; } @@ -187,6 +188,12 @@ static UICompositeViewDescription *compositeDescription = nil; [_imagesCollectionView registerClass:[UIImageViewDeletable class] forCellWithReuseIdentifier:NSStringFromClass([UIImageViewDeletable class])]; [_imagesCollectionView setDataSource: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 +209,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 +233,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 @@ -236,6 +257,7 @@ static UICompositeViewDescription *compositeDescription = nil; CGRect tableViewFrame = [_tableController.tableView frame]; tableViewFrame.size.height -= 100; [_tableController.tableView setFrame:tableViewFrame]; + [self updateFramesInclRecordingView]; } completion:nil]; } @@ -245,10 +267,20 @@ static UICompositeViewDescription *compositeDescription = nil; CGRect popupFrame = _popupMenu.frame; popupFrame.size.height = 44 * [_popupMenu numberOfRowsInSection:0]; _popupMenu.frame = popupFrame; + + // Voice recording + _vrView.hidden = true; + _preservePendingRecording = false; } - (void)viewWillDisappear:(BOOL)animated { + + if (!_preservePendingRecording) + [self cancelVoiceRecording]; + else if (_isVoiceRecording) + [self stopVoiceRecording]; + [super viewWillDisappear:animated]; [self removeCallBacks]; @@ -256,7 +288,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 +321,20 @@ static UICompositeViewDescription *compositeDescription = nil; _backButton.hidden = _tableController.isEditing; [_tableController scrollToBottom:true]; [self refreshImageDrawer]; + [self stopAllPlays]; + } #pragma mark - +- (void)applicationWillEnterBackground{ + if (!_preservePendingRecording) + [self cancelVoiceRecording]; + else if (_isVoiceRecording) + [self stopVoiceRecording]; +} + + - (void)configureForRoom:(BOOL)editing { if (!_chatRoom) { _chatView.hidden = YES; @@ -456,16 +500,24 @@ static UICompositeViewDescription *compositeDescription = nil; } } -- (BOOL)sendMessage:(NSString *)message withExterlBodyUrl:(NSURL *)externalUrl { +- (BOOL)sendMessage:(NSString *)message withExterlBodyUrl:(NSURL *)externalUrl andVoiceContent:(LinphoneContent *)voiceContent { if (_chatRoom == NULL) { LOGW(@"Cannot send message: No chatroom"); return FALSE; } - LinphoneChatMessage *msg = linphone_chat_room_create_message(_chatRoom, [message UTF8String]); + LinphoneChatMessage *msg = linphone_chat_room_create_empty_message(_chatRoom); + 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]); } + + // Voice recording + + if (voiceContent) + linphone_chat_message_add_content(msg, voiceContent); // we must ref & unref message because in case of error, it will be destroy otherwise linphone_chat_message_send(msg); @@ -512,10 +564,10 @@ static UICompositeViewDescription *compositeDescription = nil; [sheet addButtonWithTitle:NSLocalizedString(@"Send to this friend", nil) block:^() { if (![[self.messageField text] isEqualToString:@""]) { - [self sendMessageInMessageField]; + [self sendMessageInMessageFieldWithVoiceContent:nil]; } if (url) - [self sendMessage:url withExterlBodyUrl:nil]; + [self sendMessage:url withExterlBodyUrl:nil andVoiceContent:nil]; else [self startFileUpload:data withName:fileName]; }]; @@ -600,8 +652,8 @@ static UICompositeViewDescription *compositeDescription = nil; _addressLabel.frame = frame; } -- (void)sendMessageInMessageField { - if ([self sendMessage:[_messageField text] withExterlBodyUrl:nil]) { +- (void)sendMessageInMessageFieldWithVoiceContent:(LinphoneContent *)voiceContent { + if ([self sendMessage:[_messageField text] withExterlBodyUrl:nil andVoiceContent:voiceContent]) { scrollOnGrowingEnabled = FALSE; [_messageField setText:@""]; scrollOnGrowingEnabled = TRUE; @@ -657,6 +709,7 @@ static UICompositeViewDescription *compositeDescription = nil; CGRect tableRect = [_tableController.view frame]; tableRect.size.height -= diff; [_tableController.view setFrame:tableRect]; + [self updateFramesInclRecordingView]; // if we're showing the compose message, update it position if (![_composeLabel isHidden]) { @@ -681,28 +734,40 @@ static UICompositeViewDescription *compositeDescription = nil; } - (IBAction)onSendClick:(id)event { + LinphoneContent *voiceContent = nil; + if (_isPendingVoiceRecord && _voiceRecorder && linphone_recorder_get_file(_voiceRecorder)) { + voiceContent = linphone_recorder_create_content(_voiceRecorder); + _isPendingVoiceRecord = false; + [self cancelVoiceRecording]; + [self stopVoiceRecordPlayer]; + } + + if (!linphone_core_is_network_reachable(LC)) { + [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView:@"send a message"] animated:YES completion:nil]; + //return; + } if ([_fileContext count] > 0) { if (linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesConference) { - [self startMultiFilesUpload]; + [self startMultiFilesUploadWithVoiceContent:voiceContent]; } 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 voiceContent:voiceContent]; } 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 voiceContent:voiceContent]; if (![[self.messageField text] isEqualToString:@""]) { - [self sendMessage:[_messageField text] withExterlBodyUrl:nil]; + [self sendMessage:[_messageField text] withExterlBodyUrl:nil andVoiceContent:voiceContent]; } } 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] voiceContent:voiceContent]; } } [self clearMessageView]; return; } - [self sendMessageInMessageField]; + [self sendMessageInMessageFieldWithVoiceContent:voiceContent]; } - (IBAction)onListTap:(id)sender { @@ -759,14 +824,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 { + _preservePendingRecording = true; [_messageField resignFirstResponder]; [ImagePickerView SelectImageFromDevice:self atPosition:_pictureButton inView:self.view withDocumentMenuDelegate:self]; @@ -800,15 +862,15 @@ static UICompositeViewDescription *compositeDescription = nil; #pragma mark ChatRoomDelegate -- (BOOL)startMultiFilesUpload { +- (BOOL)startMultiFilesUploadWithVoiceContent:(LinphoneContent *)voiceContent { FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init]; [fileTransfer setText:[self.messageField text]]; - [fileTransfer uploadFileContent:_fileContext forChatRoom:_chatRoom]; + [fileTransfer uploadFileContent:_fileContext forChatRoom:_chatRoom andVoiceContent:voiceContent]; [_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 voiceContent:(LinphoneContent *)voiceContent { FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init]; if (message) [fileTransfer setText:message]; @@ -818,7 +880,7 @@ 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 voiceContent:voiceContent]; [_tableController scrollToBottom:true]; return TRUE; } @@ -830,26 +892,26 @@ static UICompositeViewDescription *compositeDescription = nil; return TRUE; } -- (BOOL)resendMultiFiles:(FileContext *)newFileContext message:(NSString *)message { +- (BOOL)resendMultiFiles:(FileContext *)newFileContext message:(NSString *)message voiceContent:(LinphoneContent *)voiceContent { FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init]; if (message) [fileTransfer setText:message]; - [fileTransfer uploadFileContent:newFileContext forChatRoom:_chatRoom]; + [fileTransfer uploadFileContent:newFileContext forChatRoom:_chatRoom andVoiceContent:voiceContent]; [_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 voiceContent:(LinphoneContent *)voiceContent{ 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 voiceContent:voiceContent]; [_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 voiceContent:(LinphoneContent *)voiceContent { + [self sendMessage:message withExterlBodyUrl:[NSURL URLWithString:url] andVoiceContent:voiceContent]; } #pragma mark ImagePickerDelegate @@ -1077,6 +1139,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 +1165,12 @@ static UICompositeViewDescription *compositeDescription = nil; tableViewFrame.size.height = imagesFrame.origin.y - tableViewFrame.origin.y; [_tableController.tableView setFrame:tableViewFrame]; } + if (_showVoiceRecorderView) + _vrView.hidden = true; + [self updateFramesInclRecordingView]; + } completion:^(BOOL finished){ - }]; } @@ -1158,6 +1224,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 +1237,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 +1251,13 @@ static UICompositeViewDescription *compositeDescription = nil; animated:FALSE]; } } + if (_showVoiceRecorderView) + _vrView.hidden = true; + [self updateFramesInclRecordingView]; + } completion:^(BOOL finished){ - }]; } @@ -1395,7 +1466,7 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog [imgView setUuid:[_fileContext.uuidsArray objectAtIndex:[indexPath item]]]; [imgView setDeleteDelegate:self]; [imgView setFrame:imgFrame]; - [_sendButton setEnabled:TRUE]; + [self setSendButtonState]; return imgView; } @@ -1416,10 +1487,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 updateFramesInclRecordingView]; } completion:nil]; - if ([_messageField.text isEqualToString:@""]) - [_sendButton setEnabled:FALSE]; + [self setSendButtonState]; } else { // resizing imagesView CGRect imagesFrame = [_imagesView frame]; @@ -1430,6 +1501,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 updateFramesInclRecordingView]; [_imagesCollectionView reloadData]; } } @@ -1592,4 +1664,268 @@ 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); + [CallManager.instance activateAudioSession]; +} + +-(void) cancelVoiceRecording { + _showVoiceRecorderView = false; + _toggleRecord.selected = false; + [self updateFramesInclRecordingView]; + _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]; + + _toggleRecord.selected = true; + [_vrPlayButton setImage:[UIImage imageNamed:@"vr_stop"] forState:UIControlStateNormal]; + + + _showVoiceRecorderView = true; + [self updateFramesInclRecordingView]; + _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]; + } + +// ChatConversationView *view = (__bridge ChatConversationView *)linphone_player_cbs_get_user_data(linphone_player_get_current_callbacks(p)); +// [view stopVoiceRecordPlayer]; +} + +// 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) updateFramesInclRecordingView { // place below the messages table. + BOOL showHide = _showVoiceRecorderView != !_vrView.hidden; + if (showHide) + _vrView.hidden = !_showVoiceRecorderView; + + CGRect vrFrame = _vrView.frame; + CGRect tableFrame = _tableController.tableView.frame; + if (showHide) { + 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; +} + +-(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); +} + + + @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/LinphoneManager.h b/Classes/LinphoneManager.h index f168ada5a..1caf894f7 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 kLinphoneMsgNotificationAppGroupId; diff --git a/Classes/LinphoneManager.m b/Classes/LinphoneManager.m index 64bb1b296..47b288df4 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 kLinphoneMsgNotificationAppGroupId = @"group.org.linphone.phone.msgNotification"; @@ -1784,7 +1786,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; } diff --git a/Classes/LinphoneUI/Base.lproj/UIChatBubblePhotoCell.xib b/Classes/LinphoneUI/Base.lproj/UIChatBubblePhotoCell.xib index d320d6580..b987f08b4 100644 --- a/Classes/LinphoneUI/Base.lproj/UIChatBubblePhotoCell.xib +++ b/Classes/LinphoneUI/Base.lproj/UIChatBubblePhotoCell.xib @@ -33,11 +33,16 @@ + + + + + - + @@ -57,11 +62,11 @@ - + - + @@ -158,28 +163,60 @@ - + + + + + + + + + + + + + + + + + + + + @@ -217,5 +254,7 @@ + + diff --git a/Classes/LinphoneUI/UIChatBubblePhotoCell.h b/Classes/LinphoneUI/UIChatBubblePhotoCell.h index 43b6b6eed..c1530c413 100644 --- a/Classes/LinphoneUI/UIChatBubblePhotoCell.h +++ b/Classes/LinphoneUI/UIChatBubblePhotoCell.h @@ -44,6 +44,16 @@ @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; diff --git a/Classes/LinphoneUI/UIChatBubblePhotoCell.m b/Classes/LinphoneUI/UIChatBubblePhotoCell.m index d96ae3cb2..4e6766faa 100644 --- a/Classes/LinphoneUI/UIChatBubblePhotoCell.m +++ b/Classes/LinphoneUI/UIChatBubblePhotoCell.m @@ -26,6 +26,11 @@ #import #import +#define voicePlayer VIEW(ChatConversationView).sharedVoicePlayer +#define chatView VIEW(ChatConversationView) + + + @implementation UIChatBubblePhotoCell { FileTransferDelegate *_ftd; CGSize imageSize, bubbleSize, videoDefaultSize; @@ -54,6 +59,8 @@ assetIsLoaded = FALSE; self.contentView.userInteractionEnabled = NO; _contentViews = [[NSMutableArray alloc] init]; + self.vrWaveMaskPlayback.layer.cornerRadius = 10.0f; + self.vrWaveMaskPlayback.layer.masksToBounds = YES; } return self; } @@ -151,32 +158,55 @@ }); } + - (void)update { if (self.message == nil) { LOGW(@"Cannot update message room cell: NULL message"); return; } [super update]; - + + 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 +244,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); @@ -713,16 +741,95 @@ 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; + } 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]; + } +} + @end diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.h b/Classes/LinphoneUI/UIChatBubbleTextCell.h index 6778a8143..062164d5f 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.h +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.h @@ -26,6 +26,9 @@ #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 @@ -72,5 +75,6 @@ + (NSString *)TextMessageForChat:(LinphoneChatMessage *)message; + (CGSize)computeBoundingBox:(NSString *)text size:(CGSize)size font:(UIFont *)font; + (NSString *)ContactDateForChat:(LinphoneChatMessage *)message; ++(LinphoneContent *) voiceContent:(LinphoneChatMessage *)message; @end diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.m b/Classes/LinphoneUI/UIChatBubbleTextCell.m index 808cb20b1..fc3371774 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.m +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.m @@ -306,6 +306,12 @@ } - (void)onResend { + + if (!linphone_core_is_network_reachable(LC)) { + [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView:@"send a message"] animated:YES completion:nil]; + //return; + } + if (_message == nil || !linphone_chat_message_is_outgoing(_message)) return; @@ -314,7 +320,13 @@ 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); + LinphoneContent *voiceContent = [UIChatBubbleTextCell voiceContent:_message]; + size_t contentCount = bctbx_list_size(contents); + if (voiceContent) + contentCount--; + + BOOL multiParts = ((linphone_chat_message_get_text_content(_message) != NULL) ? contentCount > 2 : contentCount > 1); + if (multiParts) { FileContext *newfileContext = [[FileContext alloc] init]; [newfileContext clear]; @@ -323,6 +335,9 @@ 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_voice_recording(content)) { + continue; + } 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]; @@ -335,11 +350,11 @@ [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]; + [_chatRoomDelegate resendMultiFiles:newfileContext message: text? [NSString stringWithUTF8String:text]: NULL voiceContent:voiceContent]; }); return; } - if (linphone_chat_message_get_file_transfer_information(_message) != NULL) { + if (!voiceContent && contentCount == 1 && 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]; @@ -354,11 +369,11 @@ 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]; + [_chatRoomDelegate resendFile: (data?:[ChatConversationView getFileData:localImage]) withName:localImage type:@"image" key:@"localimage" message:str voiceContent:voiceContent]; } else if (localVideo) { - [_chatRoomDelegate resendFile:(data?:[ChatConversationView getFileData:localVideo]) withName:localVideo type:@"video" key:@"localvideo" message:str]; + [_chatRoomDelegate resendFile:(data?:[ChatConversationView getFileData:localVideo]) withName:localVideo type:@"video" key:@"localvideo" message:str voiceContent:voiceContent]; } else { - [_chatRoomDelegate resendFile:(data?:[ChatConversationView getFileData:localFile]) withName:localFile type:@"image" key:@"localfile" message:str]; + [_chatRoomDelegate resendFile:(data?:[ChatConversationView getFileData:localFile]) withName:localFile type:@"image" key:@"localfile" message:str voiceContent:voiceContent]; } }); } else { @@ -366,7 +381,10 @@ 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]; + NSString *text = self.textMessage; + if (voiceContent && [text isEqualToString:@"🗻"]) + text = nil; + [_chatRoomDelegate resendChat:text withExternalUrl:nil voiceContent:voiceContent]; }); } } @@ -457,6 +475,20 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; return image; } ++(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 +516,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,14 +607,15 @@ 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); + LinphoneContent *fileContent = linphone_chat_message_get_utf8_text(chat) ? nil : linphone_chat_message_get_file_transfer_information(chat); if (url == nil && fileContent == NULL) { size = [self computeBoundingBox:messageText size:CGSizeMake(width - CELL_MESSAGE_X_MARGIN - 4, CGFLOAT_MAX) font:messageFont]; } else { + NSString *localImage = [LinphoneManager getMessageAppDataForKey:@"localimage" inMessage:chat]; NSString *localFile = [LinphoneManager getMessageAppDataForKey:@"localfile" inMessage:chat]; NSString *localVideo = [LinphoneManager getMessageAppDataForKey:@"localvideo" inMessage:chat]; @@ -583,27 +653,43 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; image = [[UIImage alloc] initWithData:data]; } } else { - return [self ViewHeightForFile:width]; + CGSize fileSize = [self ViewHeightForFile:width]; + if (voiceContent) { + fileSize = [self addVoicePlayerToSize:fileSize withMargins:true]; + } + return fileSize; } 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(CELL_MIN_WIDTH + CELL_MESSAGE_X_MARGIN, CELL_MIN_HEIGHT + CELL_MESSAGE_Y_MARGIN + textSize.height + 20); + if (voiceContent) { + baseSize = [self addVoicePlayerToSize:baseSize withMargins:true]; + } + 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 +701,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 +717,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); diff --git a/Classes/PhoneMainView.m b/Classes/PhoneMainView.m index c8d78cd4f..680712541 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; } diff --git a/Classes/Utils/FileTransferDelegate.h b/Classes/Utils/FileTransferDelegate.h index 3aa3d22db..21ff1d605 100644 --- a/Classes/Utils/FileTransferDelegate.h +++ b/Classes/Utils/FileTransferDelegate.h @@ -24,8 +24,8 @@ @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 andVoiceContent:(LinphoneContent *)voiceContent; +- (void)uploadData:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom type:(NSString *)type subtype:(NSString *)subtype name:(NSString *)name key:(NSString *)key voiceContent:(LinphoneContent *)voiceContent; - (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; diff --git a/Classes/Utils/FileTransferDelegate.m b/Classes/Utils/FileTransferDelegate.m index d8d20670b..9ad65457e 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 voiceContent:(LinphoneContent *)voiceContent{ if ([[LinphoneManager.instance fileTransferDelegates] containsObject:self]) { LOGW(@"fileTransferDelegates has already added %p", self); return; @@ -124,12 +124,15 @@ 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]; + + if (voiceContent) + linphone_chat_message_add_content(_message, voiceContent); 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 andVoiceContent:(LinphoneContent *)voiceContent{ [LinphoneManager.instance.fileTransferDelegates addObject:self]; _message = linphone_chat_room_create_empty_message(chatRoom); @@ -162,6 +165,8 @@ 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]; + if (voiceContent) + linphone_chat_message_add_content(_message, voiceContent); LOGI(@"%p Uploading content from message %p", self, _message); linphone_chat_message_send(_message); } @@ -170,12 +175,12 @@ 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" voiceContent: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"]; + [self uploadData:data forChatRoom:chatRoom type:@"video" subtype:@"mov" name:name key:@"localvideo" voiceContent:nil]; } - (void)uploadFile:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom withName:(NSString *)name { @@ -184,7 +189,7 @@ static void file_transfer_progress_indication_send(LinphoneChatMessage *message, 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 voiceContent:nil]; } - (BOOL)download:(LinphoneChatMessage *)message { diff --git a/Classes/Utils/Utils.h b/Classes/Utils/Utils.h index 7289bbe83..d515926af 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, diff --git a/Classes/Utils/Utils.m b/Classes/Utils/Utils.m index 4f6d1249d..7ae1fedca 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) 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..318063fa6 100644 Binary files a/Resources/fr.lproj/Localizable.strings and b/Resources/fr.lproj/Localizable.strings 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 c1a97bd18..035b2db19 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 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; + 61AEBEA321906AFC00F35E7F /* (null) 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 /* BuildFile in Resources */ = {isa = PBXBuildFile; }; + 63E802DB1C625AEF000D5509 /* (null) 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,6 +675,12 @@ 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 */; }; @@ -1714,6 +1720,12 @@ 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 = ""; }; @@ -1951,7 +1963,7 @@ files = ( 61DD7E1F2372E88F001BDD01 /* CoreLocation.framework in Frameworks */, 6180D6FE21EE41A800AD9CB6 /* QuickLook.framework in Frameworks */, - 61AEBEA321906AFC00F35E7F /* BuildFile in Frameworks */, + 61AEBEA321906AFC00F35E7F /* (null) in Frameworks */, D37DC7181594AF3400B2A5EB /* MessageUI.framework in Frameworks */, 61F1997520C6B1D5006B069A /* AVKit.framework in Frameworks */, 249660951FD6A35F001D55AA /* Photos.framework in Frameworks */, @@ -2285,7 +2297,7 @@ path = LinphoneUI; sourceTree = ""; }; - 29B97314FDCFA39411CA2CEA = { + 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { isa = PBXGroup; children = ( 8C23BCB71D82AAC3005F19BB /* linphone.entitlements */, @@ -2447,6 +2459,12 @@ 633FEBE11D3CD5570014B822 /* images */ = { isa = PBXGroup; children = ( + 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 */, @@ -3357,7 +3375,7 @@ fr, hu, ); - mainGroup = 29B97314FDCFA39411CA2CEA; + mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; productRefGroup = 19C28FACFE9D520D11CA2CBB /* Products */; projectDirPath = ""; projectRoot = ""; @@ -3408,6 +3426,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 +3437,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 /* BuildFile in Resources */, + 63E802DB1C625AEF000D5509 /* (null) in Resources */, 633FEE2E1D3CD5590014B822 /* color_F.png in Resources */, 633FEDC51D3CD5590014B822 /* call_hangup_disabled@2x.png in Resources */, 633FEEDF1D3CD55A0014B822 /* numpad_8_over~ipad.png in Resources */, @@ -3453,6 +3472,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 +3498,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 +3526,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 */, @@ -3740,6 +3762,7 @@ 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 */, 633FEDF01D3CD5590014B822 /* call_transfer_disabled.png in Resources */, 633FEE351D3CD5590014B822 /* conference_exit_default@2x.png in Resources */, @@ -3772,6 +3795,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 */,