/* * 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 #import #import #import #import #import #import #import "CallView.h" #import "CallSideMenuView.h" #import "LinphoneManager.h" #import "PhoneMainView.h" #import "Utils.h" #include "linphone/linphonecore.h" #import "linphoneapp-Swift.h" const NSInteger SECURE_BUTTON_TAG = 5; @implementation CallView { BOOL hiddenVolume; } #pragma mark - Lifecycle Functions - (id)init { self = [super initWithNibName:NSStringFromClass(self.class) bundle:[NSBundle mainBundle]]; if (self != nil) { singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControls:)]; videoZoomHandler = [[VideoZoomHandler alloc] init]; videoHidden = TRUE; [self updateCallView]; } return self; } #pragma mark - UICompositeViewDelegate Functions static UICompositeViewDescription *compositeDescription = nil; + (UICompositeViewDescription *)compositeViewDescription { if (compositeDescription == nil) { compositeDescription = [[UICompositeViewDescription alloc] init:self.class statusBar:StatusBarView.class tabBar:nil sideMenu:CallSideMenuView.class fullscreen:false isLeftFragment:YES fragmentWith:nil]; compositeDescription.darkBackground = true; } return compositeDescription; } - (UICompositeViewDescription *)compositeViewDescription { return self.class.compositeViewDescription; } #pragma mark - ViewController Functions - (void)viewDidLoad { [super viewDidLoad]; _routesEarpieceButton.enabled = !IPAD; // TODO: fixme! video preview frame is too big compared to openGL preview // frame, so until this is fixed, temporary disabled it. #if 0 _videoPreview.layer.borderColor = UIColor.whiteColor.CGColor; _videoPreview.layer.borderWidth = 1; #endif [singleFingerTap setNumberOfTapsRequired:1]; [singleFingerTap setCancelsTouchesInView:FALSE]; [self.videoView addGestureRecognizer:singleFingerTap]; [videoZoomHandler setup:_videoGroup]; _videoGroup.alpha = 0; [_videoCameraSwitch setPreview:_videoPreview]; UIPanGestureRecognizer *dragndrop = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveVideoPreview:)]; dragndrop.minimumNumberOfTouches = 1; [_videoPreview addGestureRecognizer:dragndrop]; [_zeroButton setDigit:'0']; [_zeroButton setDtmf:true]; [_oneButton setDigit:'1']; [_oneButton setDtmf:true]; [_twoButton setDigit:'2']; [_twoButton setDtmf:true]; [_threeButton setDigit:'3']; [_threeButton setDtmf:true]; [_fourButton setDigit:'4']; [_fourButton setDtmf:true]; [_fiveButton setDigit:'5']; [_fiveButton setDtmf:true]; [_sixButton setDigit:'6']; [_sixButton setDtmf:true]; [_sevenButton setDigit:'7']; [_sevenButton setDtmf:true]; [_eightButton setDigit:'8']; [_eightButton setDtmf:true]; [_nineButton setDigit:'9']; [_nineButton setDtmf:true]; [_starButton setDigit:'*']; [_starButton setDtmf:true]; [_hashButton setDigit:'#']; [_hashButton setDtmf:true]; } - (void)dealloc { [PhoneMainView.instance.view removeGestureRecognizer:singleFingerTap]; // Remove all observer [NSNotificationCenter.defaultCenter removeObserver:self]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; _waitView.hidden = TRUE; CallManager.instance.nextCallIsTransfer = FALSE; callRecording = FALSE; _recordButtonOnView.hidden = TRUE; // Update on show [self hideRoutes:TRUE animated:FALSE]; [self hideOptions:TRUE animated:FALSE]; [self hidePad:TRUE animated:FALSE]; [self hideSpeaker:LinphoneManager.instance.bluetoothAvailable]; [self callDurationUpdate]; [self onCurrentCallChange]; // Set windows (warn memory leaks) linphone_core_set_native_video_window_id(LC, (__bridge void *)(_videoView)); linphone_core_set_native_preview_window_id(LC, (__bridge void *)(_videoPreview)); [self previewTouchLift]; // Enable tap [singleFingerTap setEnabled:TRUE]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(messageReceived:) name:kLinphoneMessageReceived object:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(bluetoothAvailabilityUpdateEvent:) name:kLinphoneBluetoothAvailabilityUpdate object:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(callUpdateEvent:) name:kLinphoneCallUpdate object:nil]; [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(callDurationUpdate) userInfo:nil repeats:YES]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [[UIApplication sharedApplication] setIdleTimerDisabled:YES]; [[UIDevice currentDevice] setProximityMonitoringEnabled:TRUE]; [PhoneMainView.instance setVolumeHidden:TRUE]; hiddenVolume = TRUE; // we must wait didAppear to reset fullscreen mode because we cannot change it in viewwillappear LinphoneCall *call = linphone_core_get_current_call(LC); LinphoneCallState state = (call != NULL) ? linphone_call_get_state(call) : 0; [self callUpdate:call state:state animated:FALSE]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [[UIDevice currentDevice] setProximityMonitoringEnabled:FALSE]; [self disableVideoDisplay:TRUE animated:NO]; if (hideControlsTimer != nil) { [hideControlsTimer invalidate]; hideControlsTimer = nil; } if (hiddenVolume) { [PhoneMainView.instance setVolumeHidden:FALSE]; hiddenVolume = FALSE; } if (videoDismissTimer) { [self dismissVideoActionSheet:videoDismissTimer]; [videoDismissTimer invalidate]; videoDismissTimer = nil; } // Remove observer [NSNotificationCenter.defaultCenter removeObserver:self]; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [[UIApplication sharedApplication] setIdleTimerDisabled:false]; [[UIDevice currentDevice] setProximityMonitoringEnabled:FALSE]; [PhoneMainView.instance fullScreen:false]; // Disable tap [singleFingerTap setEnabled:FALSE]; if (linphone_core_get_calls_nb(LC) == 0) { // reseting speaker button because no more call _speakerButton.selected = FALSE; } NSString *address = [LinphoneManager.instance lpConfigStringForKey:@"sas_dialog_denied"]; if (address) { UIConfirmationDialog *securityDialog = [UIConfirmationDialog ShowWithMessage:NSLocalizedString(@"Trust has been denied. Make a call to start the authentication process again.", nil) cancelMessage:NSLocalizedString(@"CANCEL", nil) confirmMessage:NSLocalizedString(@"CALL", nil) onCancelClick:^() { } onConfirmationClick:^() { LinphoneAddress *addr = linphone_address_new(address.UTF8String); [CallManager.instance startCallWithAddr:addr isSas:TRUE]; linphone_address_unref(addr); } ]; [securityDialog.securityImage setImage:[UIImage imageNamed:@"security_alert_indicator.png"]]; securityDialog.securityImage.hidden = FALSE; [securityDialog setSpecialColor]; [LinphoneManager.instance lpConfigSetString:nil forKey:@"sas_dialog_denied"]; } } - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; [self updateUnreadMessage:NO]; [self previewTouchLift]; [_recordButtonOnView setHidden:!callRecording]; [self updateCallView]; LinphoneCall *call = linphone_core_get_current_call(LC) ; if (call && linphone_call_get_state(call) == LinphoneCallStatePausedByRemote) { _pausedByRemoteView.hidden = NO; [self updateInfoView:TRUE]; } _conferenceView.hidden = !linphone_core_is_in_conference(LC); } #pragma mark - UI modification - (void)updateInfoView:(BOOL)pausedByRemote { CGRect infoFrame = _infoView.frame; if (pausedByRemote || !videoHidden) { infoFrame.origin.y = 0; } else { infoFrame.origin.y = (_avatarImage.frame.origin.y-66)/2; } _infoView.frame = infoFrame; } - (void)updateCallView { CGRect pauseFrame = _callPauseButton.frame; CGRect recordFrame = _recordButtonOnView.frame; if (videoHidden) { pauseFrame.origin.y = _bottomBar.frame.origin.y - pauseFrame.size.height - 60; } else { pauseFrame.origin.y = _videoCameraSwitch.frame.origin.y+_videoGroup.frame.origin.y; } recordFrame.origin.y = _bottomBar.frame.origin.y - pauseFrame.size.height - 60; _callPauseButton.frame = pauseFrame; _recordButtonOnView.frame = recordFrame; [self updateInfoView:FALSE]; } - (void)hideSpinnerIndicator:(LinphoneCall *)call { _videoWaitingForFirstImage.hidden = TRUE; } static void hideSpinner(LinphoneCall *call, void *user_data) { CallView *thiz = (__bridge CallView *)user_data; [thiz hideSpinnerIndicator:call]; } - (void)updateBottomBar:(LinphoneCall *)call state:(LinphoneCallState)state { [_speakerButton update]; [_microButton update]; [_callPauseButton update]; [_conferencePauseButton update]; [_videoButton update]; [_hangupButton update]; _optionsButton.enabled = (!call || !linphone_core_sound_resources_locked(LC)); _optionsTransferButton.enabled = call && !linphone_core_sound_resources_locked(LC); // enable conference button if 2 calls are presents and at least one is not in the conference int confSize = linphone_core_get_conference_size(LC) - (linphone_core_is_in_conference(LC) ? 1 : 0); _optionsConferenceButton.enabled = ((linphone_core_get_calls_nb(LC) > 1) && (linphone_core_get_calls_nb(LC) != confSize)); // Disable transfert in conference if (linphone_core_get_current_call(LC) == NULL) { [_optionsTransferButton setEnabled:FALSE]; } else { [_optionsTransferButton setEnabled:TRUE]; } switch (state) { case LinphoneCallEnd: case LinphoneCallError: case LinphoneCallIncoming: case LinphoneCallOutgoing: [self hidePad:TRUE animated:TRUE]; [self hideOptions:TRUE animated:TRUE]; [self hideRoutes:TRUE animated:TRUE]; default: break; } } - (void)toggleControls:(id)sender { bool controlsHidden = (_bottomBar.alpha == 0.0); [self hideControls:!controlsHidden sender:sender]; } - (void)timerHideControls:(id)sender { [self hideControls:TRUE sender:sender]; } - (void)hideControls:(BOOL)hidden sender:(id)sender { if (videoHidden && hidden) return; if (hideControlsTimer) { [hideControlsTimer invalidate]; hideControlsTimer = nil; } if ([[PhoneMainView.instance currentView] equal:CallView.compositeViewDescription]) { // show controls [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.35]; _pausedCallsTable.tableView.alpha = _videoCameraSwitch.alpha = _callPauseButton.alpha = _routesView.alpha = _optionsView.alpha = _numpadView.alpha = _bottomBar.alpha = (hidden ? 0 : 1); _infoView.alpha = (hidden ? 0 : .8f); [UIView commitAnimations]; [PhoneMainView.instance hideTabBar:hidden]; [PhoneMainView.instance hideStatusBar:hidden]; if (!hidden) { // hide controls in 5 sec hideControlsTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(timerHideControls:) userInfo:nil repeats:NO]; } } } - (void)disableVideoDisplay:(BOOL)disabled animated:(BOOL)animation { if (disabled == videoHidden && animation) return; videoHidden = disabled; if (!disabled) { [videoZoomHandler resetZoom]; } if (animation) { [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:1.0]; } [_videoGroup setAlpha:disabled ? 0 : 1]; [self hideControls:!disabled sender:nil]; if (animation) { [UIView commitAnimations]; } // only show camera switch button if we have more than 1 camera _videoCameraSwitch.hidden = (disabled || !LinphoneManager.instance.frontCamId); _videoPreview.hidden = (disabled || !linphone_core_self_view_enabled(LC)); if (hideControlsTimer != nil) { [hideControlsTimer invalidate]; hideControlsTimer = nil; } if(![PhoneMainView.instance isIphoneXDevice]){ [PhoneMainView.instance fullScreen:!disabled]; } [PhoneMainView.instance hideTabBar:!disabled]; if (!disabled) { #ifdef TEST_VIDEO_VIEW_CHANGE [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(_debugChangeVideoView) userInfo:nil repeats:YES]; #endif // [self batteryLevelChanged:nil]; [_videoWaitingForFirstImage setHidden:NO]; [_videoWaitingForFirstImage startAnimating]; LinphoneCall *call = linphone_core_get_current_call(LC); // linphone_call_params_get_used_video_codec return 0 if no video stream enabled if (call != NULL && linphone_call_params_get_used_video_codec(linphone_call_get_current_params(call))) { linphone_call_set_next_video_frame_decoded_callback(call, hideSpinner, (__bridge void *)(self)); } } } - (void)displayVideoCall:(BOOL)animated { [self disableVideoDisplay:FALSE animated:animated]; } - (void)displayAudioCall:(BOOL)animated { [self disableVideoDisplay:TRUE animated:animated]; } - (void)callDurationUpdate { int duration = linphone_core_get_current_call(LC) ? linphone_call_get_duration(linphone_core_get_current_call(LC)) : 0; _durationLabel.text = [LinphoneUtils durationToString:duration]; [_pausedCallsTable update]; [_conferenceCallsTable update]; } - (void)onCurrentCallChange { LinphoneCall *call = linphone_core_get_current_call(LC); _noActiveCallView.hidden = (call || linphone_core_is_in_conference(LC)); _callView.hidden = !call; _conferenceView.hidden = !linphone_core_is_in_conference(LC); _callPauseButton.hidden = !call && !linphone_core_is_in_conference(LC); [_callPauseButton setType:UIPauseButtonType_CurrentCall call:call]; [_conferencePauseButton setType:UIPauseButtonType_Conference call:call]; if (!_callView.hidden) { const LinphoneAddress *addr = linphone_call_get_remote_address(call); [ContactDisplay setDisplayNameLabel:_nameLabel forAddress:addr]; char *uri = linphone_address_as_string_uri_only(addr); ms_free(uri); [_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:YES withRoundedRadius:YES]; } } - (void)hidePad:(BOOL)hidden animated:(BOOL)animated { if (hidden) { [_numpadButton setOff]; } else { [_numpadButton setOn]; } if (hidden != _numpadView.hidden) { if (animated) { [self hideAnimation:hidden forView:_numpadView completion:nil]; } else { [_numpadView setHidden:hidden]; } } } - (void)hideRoutes:(BOOL)hidden animated:(BOOL)animated { if (hidden) { [_routesButton setOff]; } else { [_routesButton setOn]; } _routesBluetoothButton.selected = [CallManager.instance isBluetoothEnabled]; _routesSpeakerButton.selected = [CallManager.instance isSpeakerEnabled]; _routesEarpieceButton.selected = !_routesBluetoothButton.selected && !_routesSpeakerButton.selected; if (hidden != _routesView.hidden) { if (animated) { [self hideAnimation:hidden forView:_routesView completion:nil]; } else { [_routesView setHidden:hidden]; } } } - (void)hideOptions:(BOOL)hidden animated:(BOOL)animated { if (hidden) { [_optionsButton setOff]; } else { [_optionsButton setOn]; } if (hidden != _optionsView.hidden) { if (animated) { [self hideAnimation:hidden forView:_optionsView completion:nil]; } else { [_optionsView setHidden:hidden]; } } } - (void)hideSpeaker:(BOOL)hidden { _speakerButton.hidden = hidden; _routesButton.hidden = !hidden; } #pragma mark - Event Functions - (void)bluetoothAvailabilityUpdateEvent:(NSNotification *)notif { bool available = [[notif.userInfo objectForKey:@"available"] intValue]; dispatch_async(dispatch_get_main_queue(), ^{ [self hideSpeaker:available]; }); } - (void)callUpdateEvent:(NSNotification *)notif { LinphoneCall *call = [[notif.userInfo objectForKey:@"call"] pointerValue]; LinphoneCallState state = [[notif.userInfo objectForKey:@"state"] intValue]; [self callUpdate:call state:state animated:TRUE]; } - (void)callUpdate:(LinphoneCall *)call state:(LinphoneCallState)state animated:(BOOL)animated { [self updateBottomBar:call state:state]; if (hiddenVolume) { [PhoneMainView.instance setVolumeHidden:FALSE]; hiddenVolume = FALSE; } // Update tables [_pausedCallsTable update]; [_conferenceCallsTable update]; static LinphoneCall *currentCall = NULL; if (!currentCall || linphone_core_get_current_call(LC) != currentCall) { currentCall = linphone_core_get_current_call(LC); [self onCurrentCallChange]; } // Fake call update if (call == NULL) { return; } BOOL shouldDisableVideo = !currentCall || !linphone_call_params_video_enabled(linphone_call_get_current_params(currentCall)); if (videoHidden != shouldDisableVideo) { if (!shouldDisableVideo) { [self displayVideoCall:animated]; } else { [self displayAudioCall:animated]; } } if (!shouldDisableVideo && !linphone_core_is_in_conference(LC) && // camera is diabled duiring conference, it must be activated after leaving conference. [UIApplication sharedApplication].applicationState == UIApplicationStateActive) { // Camera should not be enabled when in background) linphone_call_enable_camera(call, TRUE); } [self updateCallView]; if (state != LinphoneCallPausedByRemote) { _pausedByRemoteView.hidden = YES; } switch (state) { case LinphoneCallIncomingReceived: case LinphoneCallOutgoingInit: case LinphoneCallConnected: case LinphoneCallStreamsRunning: { // check video, because video can be disabled because of the low bandwidth. if (!linphone_call_params_video_enabled(linphone_call_get_current_params(call))) { const LinphoneCallParams *param = linphone_call_get_current_params(call); CallAppData *data = [CallManager getAppDataWithCall:call]; if (state == LinphoneCallStreamsRunning && data && data.videoRequested && linphone_call_params_low_bandwidth_enabled(param)) { // too bad video was not enabled because low bandwidth UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Low bandwidth", nil) message:NSLocalizedString(@"Video cannot be activated because of low bandwidth " @"condition, only audio is available", nil) preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Continue", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}]; [errView addAction:defaultAction]; [self presentViewController:errView animated:YES completion:nil]; data.videoRequested = FALSE; [CallManager setAppDataWithCall:call appData:data]; } } break; } case LinphoneCallUpdatedByRemote: { const LinphoneCallParams *current = linphone_call_get_current_params(call); const LinphoneCallParams *remote = linphone_call_get_remote_params(call); /* remote wants to add video */ if ((linphone_core_video_display_enabled(LC) && !linphone_call_params_video_enabled(current) && linphone_call_params_video_enabled(remote)) && (!linphone_core_get_video_policy(LC)->automatically_accept || (([UIApplication sharedApplication].applicationState != UIApplicationStateActive) && floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max))) { linphone_core_defer_call_update(LC, call); [self displayAskToEnableVideoCall:call]; } else if (linphone_call_params_video_enabled(current) && !linphone_call_params_video_enabled(remote)) { [self displayAudioCall:animated]; } break; } case LinphoneCallPausing: case LinphoneCallPaused: [self displayAudioCall:animated]; break; case LinphoneCallPausedByRemote: [self displayAudioCall:animated]; if (call == linphone_core_get_current_call(LC)) { _pausedByRemoteView.hidden = NO; [self updateInfoView:TRUE]; } break; case LinphoneCallEnd: case LinphoneCallError: default: break; } } #pragma mark - ActionSheet Functions - (void)displayAskToEnableVideoCall:(LinphoneCall *)call { if (linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call))) { return; } if (linphone_core_get_video_policy(LC)->automatically_accept && !([UIApplication sharedApplication].applicationState != UIApplicationStateActive)) return; NSString *username = [FastAddressBook displayNameForAddress:linphone_call_get_remote_address(call)]; NSString *title = [NSString stringWithFormat:NSLocalizedString(@"%@ would like to enable video", nil), username]; if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive && floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max) { UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; content.title = NSLocalizedString(@"Video request", nil); content.body = title; content.categoryIdentifier = @"video_request"; content.userInfo = @{ @"CallId" : [NSString stringWithUTF8String:linphone_call_log_get_call_id(linphone_call_get_call_log(call))] }; UNNotificationRequest *req = [UNNotificationRequest requestWithIdentifier:@"video_request" content:content trigger:NULL]; [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:req withCompletionHandler:^(NSError *_Nullable error) { // Enable or disable features based on authorization. if (error) { LOGD(@"Error while adding notification request :"); LOGD(error.description); } }]; } else { UIConfirmationDialog *sheet = [UIConfirmationDialog ShowWithMessage:title cancelMessage:nil confirmMessage:NSLocalizedString(@"ACCEPT", nil) onCancelClick:^() { LOGI(@"User declined video proposal"); if (call == linphone_core_get_current_call(LC)) { [CallManager.instance acceptVideoWithCall:call confirm:FALSE]; [videoDismissTimer invalidate]; videoDismissTimer = nil; } } onConfirmationClick:^() { LOGI(@"User accept video proposal"); if (call == linphone_core_get_current_call(LC)) { [CallManager.instance acceptVideoWithCall:call confirm:TRUE]; [videoDismissTimer invalidate]; videoDismissTimer = nil; } } inController:self]; videoDismissTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(dismissVideoActionSheet:) userInfo:sheet repeats:NO]; } } - (void)dismissVideoActionSheet:(NSTimer *)timer { UIConfirmationDialog *sheet = (UIConfirmationDialog *)timer.userInfo; [sheet dismiss]; } #pragma mark VideoPreviewMoving - (void)moveVideoPreview:(UIPanGestureRecognizer *)dragndrop { CGPoint center = [dragndrop locationInView:_videoPreview.superview]; _videoPreview.center = center; if (dragndrop.state == UIGestureRecognizerStateEnded) { [self previewTouchLift]; } } - (CGFloat)coerce:(CGFloat)value betweenMin:(CGFloat)min andMax:(CGFloat)max { return MAX(min, MIN(value, max)); } - (void)previewTouchLift { CGRect previewFrame = _videoPreview.frame; previewFrame.origin.x = [self coerce:previewFrame.origin.x betweenMin:5 andMax:(UIScreen.mainScreen.bounds.size.width - 5 - previewFrame.size.width)]; previewFrame.origin.y = [self coerce:previewFrame.origin.y betweenMin:5 andMax:(UIScreen.mainScreen.bounds.size.height - 5 - previewFrame.size.height)]; if (!CGRectEqualToRect(previewFrame, _videoPreview.frame)) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [UIView animateWithDuration:0.3 animations:^{ LOGD(@"Recentering preview to %@", NSStringFromCGRect(previewFrame)); _videoPreview.frame = previewFrame; }]; }); } } #pragma mark - Action Functions - (IBAction)onNumpadClick:(id)sender { if ([_numpadView isHidden]) { [self hidePad:FALSE animated:ANIMATED]; } else { [self hidePad:TRUE animated:ANIMATED]; } } - (IBAction)onChatClick:(id)sender { const LinphoneCall *currentCall = linphone_core_get_current_call(LC); const LinphoneAddress *addr = currentCall ? linphone_call_get_remote_address(currentCall) : NULL; // TODO encrpted or unencrpted [LinphoneManager.instance lpConfigSetBool:TRUE forKey:@"create_chat"]; [PhoneMainView.instance getOrCreateOneToOneChatRoom:addr waitView:_waitView isEncrypted:FALSE]; } - (IBAction)onRecordClick:(id)sender { if (![_optionsView isHidden]) [self hideOptions:TRUE animated:ANIMATED]; if (callRecording) { [self onRecordOnViewClick:nil]; } else { LOGD(@"Recording Starts"); [_recordButton setImage:[UIImage imageNamed:@"rec_off_default.png"] forState:UIControlStateNormal]; [_recordButtonOnView setHidden:FALSE]; LinphoneCall *call = linphone_core_get_current_call(LC); linphone_call_start_recording(call); callRecording = TRUE; } } - (IBAction)onRecordOnViewClick:(id)sender { LOGD(@"Recording Stops"); [_recordButton setImage:[UIImage imageNamed:@"rec_on_default.png"] forState:UIControlStateNormal]; [_recordButtonOnView setHidden:TRUE]; LinphoneCall *call = linphone_core_get_current_call(LC); linphone_call_stop_recording(call); callRecording = FALSE; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSString *writablePath = [paths objectAtIndex:0]; writablePath = [writablePath stringByAppendingString:@"/"]; NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:writablePath error:NULL]; if (directoryContent) { return; } } - (IBAction)onRoutesBluetoothClick:(id)sender { [self hideRoutes:TRUE animated:TRUE]; [CallManager.instance changeRouteToBluetooth]; } - (IBAction)onRoutesEarpieceClick:(id)sender { [self hideRoutes:TRUE animated:TRUE]; [CallManager.instance changeRouteToDefault]; } - (IBAction)onRoutesSpeakerClick:(id)sender { [self hideRoutes:TRUE animated:TRUE]; [CallManager.instance changeRouteToSpeaker]; } - (IBAction)onRoutesClick:(id)sender { if ([_routesView isHidden]) { [self hideRoutes:FALSE animated:ANIMATED]; } else { [self hideRoutes:TRUE animated:ANIMATED]; } } - (IBAction)onOptionsClick:(id)sender { if ([_optionsView isHidden]) { [self hideOptions:FALSE animated:ANIMATED]; } else { [self hideOptions:TRUE animated:ANIMATED]; } } - (IBAction)onOptionsTransferClick:(id)sender { [self hideOptions:TRUE animated:TRUE]; DialerView *view = VIEW(DialerView); [view setAddress:@""]; CallManager.instance.nextCallIsTransfer = TRUE; [PhoneMainView.instance changeCurrentView:view.compositeViewDescription]; } - (IBAction)onOptionsAddClick:(id)sender { [self hideOptions:TRUE animated:TRUE]; DialerView *view = VIEW(DialerView); [view setAddress:@""]; CallManager.instance.nextCallIsTransfer = FALSE; [PhoneMainView.instance changeCurrentView:view.compositeViewDescription]; } - (IBAction)onOptionsConferenceClick:(id)sender { [self hideOptions:TRUE animated:TRUE]; [CallManager.instance groupCall]; } #pragma mark - Animation - (void)hideAnimation:(BOOL)hidden forView:(UIView *)target completion:(void (^)(BOOL finished))completion { if (hidden) { int original_y = target.frame.origin.y; CGRect newFrame = target.frame; newFrame.origin.y = self.view.frame.size.height; [UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{ target.frame = newFrame; } completion:^(BOOL finished) { CGRect originFrame = target.frame; originFrame.origin.y = original_y; target.hidden = YES; target.frame = originFrame; if (completion) completion(finished); }]; } else { CGRect frame = target.frame; int original_y = frame.origin.y; frame.origin.y = self.view.frame.size.height; target.frame = frame; frame.origin.y = original_y; target.hidden = NO; [UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ target.frame = frame; } completion:^(BOOL finished) { target.frame = frame; // in case application did not finish if (completion) completion(finished); }]; } } #pragma mark - Bounce - (void)messageReceived:(NSNotification *)notif { [self updateUnreadMessage:TRUE]; } - (void)updateUnreadMessage:(BOOL)appear { int unreadMessage = [LinphoneManager unreadMessageCount]; if (unreadMessage > 0) { _chatNotificationLabel.text = [NSString stringWithFormat:@"%i", unreadMessage]; [_chatNotificationView startAnimating:appear]; } else { [_chatNotificationView stopAnimating:appear]; } } @end