/*
* 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 "LinphoneAppDelegate.h"
#import "Log.h"
#import "PhoneMainView.h"
static RootViewManager *rootViewManagerInstance = nil;
@implementation RootViewManager {
PhoneMainView *currentViewController;
}
+ (void)setupWithPortrait:(PhoneMainView *)portrait {
assert(rootViewManagerInstance == nil);
rootViewManagerInstance = [[RootViewManager alloc] initWithPortrait:portrait];
}
- (instancetype)initWithPortrait:(PhoneMainView *)portrait {
self = [super init];
if (self) {
self.portraitViewController = portrait;
self.rotatingViewController = [[PhoneMainView alloc] init];
self.portraitViewController.name = @"Portrait";
self.rotatingViewController.name = @"Rotating";
currentViewController = portrait;
self.viewDescriptionStack = [NSMutableArray array];
}
return self;
}
+ (RootViewManager *)instance {
if (!rootViewManagerInstance) {
@throw [NSException exceptionWithName:@"RootViewManager" reason:@"nil instance" userInfo:nil];
}
return rootViewManagerInstance;
}
- (PhoneMainView *)currentView {
return currentViewController;
}
- (PhoneMainView *)setViewControllerForDescription:(UICompositeViewDescription *)description {
return currentViewController;
// not sure what this code was doing... but since iphone does support rotation as well now...
#if 0
if (IPAD)
return currentViewController;
PhoneMainView *newMainView = description.landscapeMode ? self.rotatingViewController : self.portraitViewController;
if (newMainView != currentViewController) {
PhoneMainView *previousMainView = currentViewController;
UIInterfaceOrientation nextViewOrientation = newMainView.interfaceOrientation;
UIInterfaceOrientation previousOrientation = currentViewController.interfaceOrientation;
LOGI(@"Changing rootViewController: %@ -> %@", currentViewController.name, newMainView.name);
currentViewController = newMainView;
LinphoneAppDelegate *delegate = (LinphoneAppDelegate *)[UIApplication sharedApplication].delegate;
if (ANIMATED) {
[UIView transitionWithView:delegate.window
duration:0.3
options:UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionAllowAnimatedContent
animations:^{
delegate.window.rootViewController = newMainView;
// when going to landscape-enabled view, we have to get the current portrait frame and orientation,
// because it could still have landscape-based size
if (nextViewOrientation != previousOrientation && newMainView == self.rotatingViewController) {
newMainView.view.frame = previousMainView.view.frame;
[newMainView.mainViewController.view setFrame:previousMainView.mainViewController.view.frame];
[newMainView willRotateToInterfaceOrientation:previousOrientation duration:0.3];
[newMainView willAnimateRotationToInterfaceOrientation:previousOrientation duration:0.3];
[newMainView didRotateFromInterfaceOrientation:nextViewOrientation];
}
}
completion:^(BOOL finished){
}];
} else {
delegate.window.rootViewController = newMainView;
// when going to landscape-enabled view, we have to get the current portrait frame and orientation,
// because it could still have landscape-based size
if (nextViewOrientation != previousOrientation && newMainView == self.rotatingViewController) {
newMainView.view.frame = previousMainView.view.frame;
[newMainView.mainViewController.view setFrame:previousMainView.mainViewController.view.frame];
[newMainView willRotateToInterfaceOrientation:previousOrientation duration:0.];
[newMainView willAnimateRotationToInterfaceOrientation:previousOrientation duration:0.];
[newMainView didRotateFromInterfaceOrientation:nextViewOrientation];
}
}
}
return currentViewController;
#endif
}
@end
@implementation PhoneMainView
@synthesize mainViewController;
@synthesize currentView;
@synthesize statusBarBG;
@synthesize volumeView;
#pragma mark - Lifecycle Functions
- (void)initPhoneMainView {
currentView = nil;
_currentRoom = NULL;
_currentName = NULL;
_previousView = nil;
inhibitedEvents = [[NSMutableArray alloc] init];
}
- (id)init {
self = [super init];
if (self) {
[self initPhoneMainView];
}
return self;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[self initPhoneMainView];
}
return self;
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (self) {
[self initPhoneMainView];
}
return self;
}
- (void)dealloc {
[NSNotificationCenter.defaultCenter removeObserver:self];
}
#pragma mark - ViewController Functions
- (void)viewDidLoad {
[super viewDidLoad];
volumeView = [[MPVolumeView alloc] initWithFrame:CGRectMake(-100, -100, 16, 16)];
volumeView.showsRouteButton = false;
volumeView.userInteractionEnabled = false;
[self.view addSubview:mainViewController.view];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Set observers
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(callUpdate:)
name:kLinphoneCallUpdate
object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(registrationUpdate:)
name:kLinphoneRegistrationUpdate
object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(textReceived:)
name:kLinphoneMessageReceived
object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(onGlobalStateChanged:)
name:kLinphoneGlobalStateUpdate
object:nil];
[[UIDevice currentDevice] setBatteryMonitoringEnabled:YES];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(batteryLevelChanged:)
name:UIDeviceBatteryLevelDidChangeNotification
object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[NSNotificationCenter.defaultCenter removeObserver:self];
[NSNotificationCenter.defaultCenter removeObserver:self name:UIDeviceBatteryLevelDidChangeNotification object:nil];
[[UIDevice currentDevice] setBatteryMonitoringEnabled:NO];
}
/* IPHONE X specific : hide the HomeIndcator when not used */
#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
#define IS_IPHONE_X (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height >= 812.0)
#define IPHONE_STATUSBAR_HEIGHT (IS_IPHONE_X ? 35 : 20)
- (BOOL)isIphoneXDevice{
return IS_IPHONE_X;
}
+ (int)iphoneStatusBarHeight{
return IPHONE_STATUSBAR_HEIGHT;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if([self isIphoneXDevice]){
if(@available(iOS 11.0, *)) {
[self childViewControllerForHomeIndicatorAutoHidden];
[self prefersHomeIndicatorAutoHidden];
[self setNeedsUpdateOfHomeIndicatorAutoHidden];
}
}
}
- (BOOL)prefersHomeIndicatorAutoHidden{
return YES;
}
- (void)setVolumeHidden:(BOOL)hidden {
// sometimes when placing a call, the volume view will appear. Inserting a
// carefully hidden MPVolumeView into the view hierarchy will hide it
if (hidden) {
if (!(volumeView.superview == self.view)) {
[self.view addSubview:volumeView];
}
} else {
if (volumeView.superview == self.view) {
[volumeView removeFromSuperview];
}
}
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
#else
- (NSUInteger)supportedInterfaceOrientations
#endif
{
return UIInterfaceOrientationMaskAll;
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration {
if (toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
return;
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
[mainViewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
[self orientationUpdate:toInterfaceOrientation];
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration {
if (toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
return;
[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
[mainViewController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
[mainViewController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
}
- (UIInterfaceOrientation)interfaceOrientation {
return [mainViewController currentOrientation];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
[mainViewController clearCache:[RootViewManager instance].viewDescriptionStack];
}
#pragma mark - Event Functions
- (void)textReceived:(NSNotification *)notif {
LinphoneChatMessage *msg = [[notif.userInfo objectForKey:@"message"] pointerValue];
NSString *callID = [notif.userInfo objectForKey:@"call-id"];
[self updateApplicationBadgeNumber];
if (!msg)
return;
if (linphone_chat_message_is_outgoing(msg))
return;
ChatConversationView *view = VIEW(ChatConversationView);
// if we already are in the conversation, we should not ring/vibrate
if (view.chatRoom && _currentRoom == view.chatRoom)
return;
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive)
return;
LinphoneManager *lm = LinphoneManager.instance;
// if the message was already received through a push notif, we don't need to ring
if (![lm popPushCallID:callID]) {
[lm playMessageSound];
}
}
- (void)registrationUpdate:(NSNotification *)notif {
LinphoneRegistrationState state = [[notif.userInfo objectForKey:@"state"] intValue];
if (state == LinphoneRegistrationFailed && ![currentView equal:AssistantView.compositeViewDescription] &&
[UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Connection failure", nil)
message:[notif.userInfo objectForKey:@"message"]
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Continue", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
[self presentViewController:errView animated:YES completion:nil];
} else if (state == LinphoneRegistrationOk && [currentView equal:ChatsListView.compositeViewDescription]) {
// update avatarImages
//ChatsListView *view = VIEW(ChatsListView);
//[view.tableController loadData];
}
}
- (void)onGlobalStateChanged:(NSNotification *)notif {
LinphoneGlobalState state = (LinphoneGlobalState)[[[notif userInfo] valueForKey:@"state"] integerValue];
static BOOL already_shown = FALSE;
if (state == LinphoneGlobalOn && !already_shown && LinphoneManager.instance.wasRemoteProvisioned) {
LinphoneAccount *account = linphone_core_get_default_account(LC);
if ([LinphoneManager.instance lpConfigBoolForKey:@"show_login_view" inSection:@"app"] && account == NULL) {
already_shown = TRUE;
AssistantView *view = VIEW(AssistantView);
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
[view fillDefaultValues];
}
}
}
- (void)callUpdate:(NSNotification *)notif {
LinphoneCall *call = [[notif.userInfo objectForKey:@"call"] pointerValue];
LinphoneCallState state = [[notif.userInfo objectForKey:@"state"] intValue];
NSString *message = [notif.userInfo objectForKey:@"message"];
switch (state) {
case LinphoneCallIncomingReceived:
if (!CallManager.callKitEnabled) {
[self displayIncomingCall:call];
}
break;
case LinphoneCallIncomingEarlyMedia: {
if (linphone_core_get_calls_nb(LC) > 1 ||
(floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max)) {
[self displayIncomingCall:call];
}
break;
}
case LinphoneCallOutgoingInit: {
[self changeCurrentView:CallOutgoingView.compositeViewDescription];
break;
}
case LinphoneCallPausedByRemote:
case LinphoneCallConnected: {
if (![LinphoneManager.instance isCTCallCenterExist]) {
/*only register CT call center CB for connected call*/
[LinphoneManager.instance setupGSMInteraction];
[[UIDevice currentDevice] setProximityMonitoringEnabled:!([CallManager.instance isSpeakerEnabled] || [CallManager.instance isBluetoothEnabled])];
}
break;
}
case LinphoneCallStreamsRunning: {
[self changeCurrentView:CallView.compositeViewDescription];
break;
}
case LinphoneCallUpdatedByRemote: {
const LinphoneCallParams *current = linphone_call_get_current_params(call);
const LinphoneCallParams *remote = linphone_call_get_remote_params(call);
if (linphone_call_params_video_enabled(current) && !linphone_call_params_video_enabled(remote)) {
[self changeCurrentView:CallView.compositeViewDescription];
}
break;
}
case LinphoneCallError: {
[self displayCallError:call message:message];
}
case LinphoneCallEnd: {
const MSList *calls = linphone_core_get_calls(LC);
if (!calls) {
while ((currentView == CallView.compositeViewDescription) ||
(currentView == CallIncomingView.compositeViewDescription) ||
(currentView == CallOutgoingView.compositeViewDescription)) {
[self popCurrentView];
}
} else {
[self changeCurrentView:CallView.compositeViewDescription];
}
break;
}
case LinphoneCallEarlyUpdatedByRemote:
case LinphoneCallEarlyUpdating:
case LinphoneCallIdle:
break;
case LinphoneCallOutgoingEarlyMedia:
case LinphoneCallOutgoingProgress: {
break;
}
case LinphoneCallReleased:
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
dispatch_async(dispatch_get_main_queue(), ^{
[PhoneMainView.instance popToView:DialerView.compositeViewDescription];
[CallManager.instance stopLinphoneCore];
});
}
break;
case LinphoneCallOutgoingRinging:
case LinphoneCallPaused:
case LinphoneCallPausing:
case LinphoneCallRefered:
break;
case LinphoneCallResuming: {
break;
}
case LinphoneCallUpdating:
break;
}
if (state == LinphoneCallEnd || state == LinphoneCallError || floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max)
[self updateApplicationBadgeNumber];
}
#pragma mark -
- (void)orientationUpdate:(UIInterfaceOrientation)orientation {
int oldLinphoneOrientation = linphone_core_get_device_rotation(LC);
int newRotation = 0;
switch (orientation) {
case UIInterfaceOrientationPortrait:
newRotation = 0;
break;
case UIInterfaceOrientationPortraitUpsideDown:
newRotation = 180;
break;
case UIInterfaceOrientationLandscapeRight:
newRotation = 270;
break;
case UIInterfaceOrientationLandscapeLeft:
newRotation = 90;
break;
default:
newRotation = oldLinphoneOrientation;
}
if (oldLinphoneOrientation != newRotation) {
linphone_core_set_device_rotation(LC, newRotation);
}
}
- (void)startUp {
@try {
LinphoneManager *lm = LinphoneManager.instance;
LOGI(@"%s", linphone_global_state_to_string(linphone_core_get_global_state(LC)));
// If we've been started by a remote push notification,
// we'll already be on the corresponding chat conversation view, no need to go anywhere else
if (![[self currentView].name isEqualToString:@"ChatConversationView"]) {
if (linphone_core_get_global_state(LC) != LinphoneGlobalOn) {
[self changeCurrentView:DialerView.compositeViewDescription];
} else if ([LinphoneManager.instance lpConfigBoolForKey:@"enable_first_login_view_preference"] == true) {
[PhoneMainView.instance changeCurrentView:FirstLoginView.compositeViewDescription];
} else {
// always start to dialer when testing
// Change to default view
const MSList *accountList = linphone_core_get_account_list(LC);
if (accountList != NULL || ([lm lpConfigBoolForKey:@"hide_assistant_preference"] == true) || lm.isTesting) {
[self changeCurrentView:DialerView.compositeViewDescription];
} else {
AssistantView *view = VIEW(AssistantView);
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
[view reset];
}
}
}
[self updateApplicationBadgeNumber]; // Update Badge at startup
} @catch (NSException *exception) {
// we'll wait until the app transitions correctly
}
}
- (void)updateApplicationBadgeNumber {
int count = 0;
count += linphone_core_get_missed_calls_count(LC);
count += [LinphoneManager unreadMessageCount];
count += linphone_core_get_calls_nb(LC);
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:count];
TabBarView *view = (TabBarView *)[PhoneMainView.instance.mainViewController getCachedController:NSStringFromClass(TabBarView.class)];
[view update:TRUE];
}
+ (CATransition *)getBackwardTransition {
BOOL RTL = [LinphoneManager langageDirectionIsRTL];
BOOL land = UIInterfaceOrientationIsLandscape([self.instance interfaceOrientation]);
NSString *transition = land ? kCATransitionFromBottom : (RTL ? kCATransitionFromRight : kCATransitionFromLeft);
CATransition *trans = [CATransition animation];
[trans setType:kCATransitionPush];
[trans setDuration:0.35];
[trans setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[trans setSubtype:transition];
return trans;
}
+ (CATransition *)getForwardTransition {
BOOL RTL = [LinphoneManager langageDirectionIsRTL];
BOOL land = UIInterfaceOrientationIsLandscape([self.instance interfaceOrientation]);
NSString *transition = land ? kCATransitionFromTop : (RTL ? kCATransitionFromLeft : kCATransitionFromRight);
CATransition *trans = [CATransition animation];
[trans setType:kCATransitionPush];
[trans setDuration:0.35];
[trans setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[trans setSubtype:transition];
return trans;
}
+ (CATransition *)getTransition:(UICompositeViewDescription *)old new:(UICompositeViewDescription *) new {
bool left = false;
if ([old equal:ChatsListView.compositeViewDescription]) {
if ([new equal:ContactsListView.compositeViewDescription] || [new equal:DialerView.compositeViewDescription] ||
[new equal:HistoryListView.compositeViewDescription]) {
left = true;
}
} else if ([old equal:SettingsView.compositeViewDescription]) {
if ([new equal:DialerView.compositeViewDescription] || [new equal:ContactsListView.compositeViewDescription] ||
[new equal:HistoryListView.compositeViewDescription] ||
[new equal:ChatsListView.compositeViewDescription]) {
left = true;
}
} else if ([old equal:DialerView.compositeViewDescription]) {
if ([new equal:ContactsListView.compositeViewDescription] ||
[new equal:HistoryListView.compositeViewDescription]) {
left = true;
}
} else if ([old equal:ContactsListView.compositeViewDescription]) {
if ([new equal:HistoryListView.compositeViewDescription]) {
left = true;
}
}
if (left) {
return [PhoneMainView getBackwardTransition];
} else {
return [PhoneMainView getForwardTransition];
}
}
+ (PhoneMainView *)instance {
return [[RootViewManager instance] currentView];
}
- (void)hideTabBar:(BOOL)hide {
[mainViewController hideTabBar:hide];
}
- (void)hideStatusBar:(BOOL)hide {
[mainViewController hideStatusBar:hide];
}
- (void)updateStatusBar:(UICompositeViewDescription *)to_view {
// Not used any more. It seems that there is no problem with new devices.
#pragma deploymate push "ignored-api-availability"
if (UIDevice.currentDevice.systemVersion.doubleValue >= 7.) {
// In iOS7, the app has a black background on dialer, incoming and incall, so we have to adjust the
// status bar style for each transition to/from these views
BOOL toLightStatus = (to_view != NULL) && ![to_view darkBackground];
if (!toLightStatus) {
// black bg: white text on black background
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
[UIView animateWithDuration:0.3f
animations:^{
statusBarBG.backgroundColor = [UIColor blackColor];
}];
} else {
// light bg: black text on white bg
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault];
[UIView animateWithDuration:0.3f
animations:^{
statusBarBG.backgroundColor = [UIColor colorWithWhite:0.935 alpha:1];
}];
}
}
#pragma deploymate pop
}
- (void)fullScreen:(BOOL)enabled {
[statusBarBG setHidden:enabled];
[mainViewController setFullscreen:enabled];
}
- (UIViewController *)popCurrentView {
NSMutableArray *viewStack = [RootViewManager instance].viewDescriptionStack;
if (viewStack.count <= 1) {
[viewStack removeAllObjects];
LOGW(@"PhoneMainView: Trying to pop view but none stacked, going to %@!",
DialerView.compositeViewDescription.name);
} else {
[viewStack removeLastObject];
LOGI(@"PhoneMainView: Popping view %@, going to %@", currentView.name,
((UICompositeViewDescription *)(viewStack.lastObject ?: DialerView.compositeViewDescription)).name);
}
[self _changeCurrentView:viewStack.lastObject ?: DialerView.compositeViewDescription
transition:[PhoneMainView getBackwardTransition]
animated:ANIMATED];
return [mainViewController getCurrentViewController];
}
- (void)changeCurrentView:(UICompositeViewDescription *)view {
[self _changeCurrentView:view transition:nil animated:ANIMATED];
}
- (UIViewController *)_changeCurrentView:(UICompositeViewDescription *)view
transition:(CATransition *)transition
animated:(BOOL)animated {
PhoneMainView *vc = [[RootViewManager instance] setViewControllerForDescription:view];
if (![view equal:vc.currentView] || vc != self) {
LOGI(@"Change current view to %@", view.name);
[self setPreviousViewName:vc.currentView.name];
NSMutableArray *viewStack = [RootViewManager instance].viewDescriptionStack;
[viewStack addObject:view];
if (animated && transition == nil)
transition = [PhoneMainView getTransition:vc.currentView new:view];
[vc.mainViewController setViewTransition:(animated ? transition : nil)];
[vc.mainViewController changeView:view];
vc->currentView = view;
}
//[[RootViewManager instance] setViewControllerForDescription:view];
NSDictionary *mdict = [NSMutableDictionary dictionaryWithObject:vc->currentView forKey:@"view"];
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneMainViewChange object:self userInfo:mdict];
return [vc->mainViewController getCurrentViewController];
}
- (UIViewController *)popToView:(UICompositeViewDescription *)view {
NSMutableArray *viewStack = [RootViewManager instance].viewDescriptionStack;
while (viewStack.count > 0 && ![[viewStack lastObject] equal:view]) {
[viewStack removeLastObject];
}
return [self _changeCurrentView:view transition:[PhoneMainView getBackwardTransition] animated:ANIMATED];
}
- (void) setPreviousViewName:(NSString*)previous{
_previousView = previous;
}
- (NSString*) getPreviousViewName {
return _previousView;
}
+ (NSString*) getPreviousViewName {
return [self getPreviousViewName];
}
- (UICompositeViewDescription *)firstView {
UICompositeViewDescription *view = nil;
NSArray *viewStack = [RootViewManager instance].viewDescriptionStack;
if ([viewStack count]) {
view = [viewStack objectAtIndex:0];
}
return view;
}
- (void)displayCallError:(LinphoneCall *)call message:(NSString *)message {
const char *lUserNameChars = linphone_address_get_username(linphone_call_get_remote_address(call));
NSString *lUserName =
lUserNameChars ? [[NSString alloc] initWithUTF8String:lUserNameChars] : NSLocalizedString(@"Unknown", nil);
NSString *lMessage;
NSString *lTitle;
// get default account
LinphoneAccount *account = linphone_core_get_default_account(LC);
if (account == nil) {
lMessage = NSLocalizedString(@"Please make sure your device is connected to the internet and double check your "
@"SIP account configuration in the settings.",
nil);
} else {
lMessage = [NSString stringWithFormat:NSLocalizedString(@"Cannot call %@.", nil), lUserName];
}
switch (linphone_call_get_reason(call)) {
case LinphoneReasonNotFound:
lMessage = [NSString stringWithFormat:NSLocalizedString(@"%@ is not connected.", nil), lUserName];
break;
case LinphoneReasonBusy:
lMessage = [NSString stringWithFormat:NSLocalizedString(@"%@ is busy.", nil), lUserName];
break;
default:
if (message != nil) {
lMessage = [NSString stringWithFormat:NSLocalizedString(@"%@\nReason was: %@", nil), lMessage, message];
}
break;
}
lTitle = NSLocalizedString(@"Call failed", nil);
UIAlertController *errView = [UIAlertController alertControllerWithTitle:lTitle
message:lMessage
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
[self presentViewController:errView animated:YES completion:nil];
}
- (void)addInhibitedEvent:(id)event {
[inhibitedEvents addObject:event];
}
- (BOOL)removeInhibitedEvent:(id)event {
NSUInteger index = [inhibitedEvents indexOfObject:event];
if (index != NSNotFound) {
[inhibitedEvents removeObjectAtIndex:index];
return TRUE;
}
return FALSE;
}
#pragma mark - ActionSheet Functions
- (void)displayIncomingCall:(LinphoneCall *)call {
LinphoneCallLog *callLog = linphone_call_get_call_log(call);
NSString *callId = [NSString stringWithUTF8String:linphone_call_log_get_call_id(callLog)];
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
LinphoneManager *lm = LinphoneManager.instance;
BOOL callIDFromPush = [lm popPushCallID:callId];
BOOL autoAnswer = [lm lpConfigBoolForKey:@"autoanswer_notif_preference"];
if (callIDFromPush && autoAnswer) {
// accept call automatically
[CallManager.instance acceptCallWithCall:call hasVideo:YES];
} else {
AudioServicesPlaySystemSound(lm.sounds.vibrate);
CallIncomingView *view = VIEW(CallIncomingView);
[self changeCurrentView:view.compositeViewDescription];
[view setCall:call];
[view setDelegate:self];
}
}
}
- (void)batteryLevelChanged:(NSNotification *)notif {
float level = [UIDevice currentDevice].batteryLevel;
UIDeviceBatteryState state = [UIDevice currentDevice].batteryState;
LOGD(@"Battery state:%d level:%.2f", state, level);
LinphoneCall *call = linphone_core_get_current_call(LC);
if (call && linphone_call_params_video_enabled(linphone_call_get_current_params(call))) {
CallAppData *data = [CallManager getAppDataWithCall:call];
if (data != nil) {
if (state == UIDeviceBatteryStateUnplugged) {
if (level <= 0.2f && !data.batteryWarningShown) {
LOGI(@"Battery warning");
DTActionSheet *sheet = [[DTActionSheet alloc]
initWithTitle:NSLocalizedString(@"Battery is running low. Stop video ?", nil)];
[sheet addCancelButtonWithTitle:NSLocalizedString(@"Continue video", nil) block:nil];
[sheet
addDestructiveButtonWithTitle:NSLocalizedString(@"Stop video", nil)
block:^() {
LinphoneCallParams *params =
linphone_core_create_call_params(LC,call);
// stop video
linphone_call_params_enable_video(params, FALSE);
linphone_call_update(call, params);
}];
[sheet showInView:self.view];
data.batteryWarningShown = TRUE;
}
}
if (level > 0.2f) {
data.batteryWarningShown = FALSE;
}
[CallManager setAppDataWithCall:call appData:data];
}
}
}
#pragma mark - IncomingCallDelegate Functions
- (void)incomingCallAborted:(LinphoneCall *)call {
}
- (void)incomingCallAccepted:(LinphoneCall *)call evenWithVideo:(BOOL)video {
[CallManager.instance acceptCallWithCall:call hasVideo:video];
}
- (void)incomingCallDeclined:(LinphoneCall *)call {
[CallManager.instance terminateCallWithCall:call];
}
#pragma mark - Chat room Functions
- (void)getOrCreateOneToOneChatRoom:(const LinphoneAddress *)remoteAddress waitView:(UIView *)waitView isEncrypted:(BOOL)isEncrypted{
if (!remoteAddress) {
[self changeCurrentView:ChatsListView.compositeViewDescription];
return;
}
if (!linphone_core_is_network_reachable(LC)) {
[PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView] animated:YES completion:nil];
return;
}
LinphoneAddress *local;
LinphoneAccount *account = linphone_core_get_default_account(LC);
if (account) {
local = linphone_address_clone(linphone_account_get_contact_address(account));
} else {
local = linphone_core_create_primary_contact_parsed(LC);
}
LinphoneChatRoom *room = linphone_core_find_one_to_one_chat_room_2(LC, local, remoteAddress, isEncrypted);
linphone_address_unref(local);
if (!room) {
bctbx_list_t *addresses = bctbx_list_new((void*)remoteAddress);
[self createChatRoom:LINPHONE_DUMMY_SUBJECT addresses:addresses andWaitView:waitView isEncrypted:isEncrypted isGroup:FALSE];
bctbx_list_free(addresses);
return;
}
[self goToChatRoom:room];
}
- (LinphoneChatRoom *)createChatRoom:(const char *)subject addresses:(bctbx_list_t *)addresses andWaitView:(UIView *)waitView isEncrypted:(BOOL)isEncrypted isGroup:(BOOL)isGroup{
LinphoneAccount *account = linphone_core_get_default_account(LC);
if (!(account && linphone_account_params_get_conference_factory_uri(linphone_account_get_params(account)))
|| ((bctbx_list_size(addresses) == 1) && !isGroup && ([[LinphoneManager instance] lpConfigBoolForKey:@"prefer_basic_chat_room" inSection:@"misc"] || !isEncrypted))) {
// If there's no factory uri, create a basic chat room
if (bctbx_list_size(addresses) != 1) {
// Display Error: unsuported group chat
UIAlertController *errView =
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"Conversation creation error", nil)
message:NSLocalizedString(@"Group conversation is not supported.", nil)
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {}];
[errView addAction:defaultAction];
[self presentViewController:errView animated:YES completion:nil];
return nil;
}
LinphoneChatRoom *basicRoom = linphone_core_get_chat_room(LC, addresses->data);
[self goToChatRoom:basicRoom];
return nil;
}
_waitView = waitView;
_waitView.hidden = NO;
// always use group chatroom
LinphoneChatRoomParams *param = linphone_core_create_default_chat_room_params(LC);
linphone_chat_room_params_enable_group(param, isGroup);
linphone_chat_room_params_enable_encryption(param, isEncrypted);
LinphoneChatRoom *room = linphone_core_create_chat_room_2(LC, param, subject ?: LINPHONE_DUMMY_SUBJECT, addresses);
if (!room) {
_waitView.hidden = YES;
return nil;
}
LinphoneChatRoomCbs *cbs = linphone_factory_create_chat_room_cbs(linphone_factory_get());
linphone_chat_room_cbs_set_state_changed(cbs, main_view_chat_room_state_changed);
linphone_chat_room_add_callbacks(room, cbs);
return room;
}
- (void)goToChatRoom:(LinphoneChatRoom *)cr {
_waitView.hidden = YES;
_waitView = NULL;
ChatConversationView *view = VIEW(ChatConversationView);
if (view.chatRoom && view.chatRoomCbs)
linphone_chat_room_remove_callbacks(view.chatRoom, view.chatRoomCbs);
view.chatRoomCbs = NULL;
if (view.chatRoom != cr)
[view clearMessageView];
view.chatRoom = cr;
view.peerAddress = linphone_address_as_string(linphone_chat_room_get_peer_address(cr));
self.currentRoom = view.chatRoom;
if (PhoneMainView.instance.currentView == view.compositeViewDescription)
[view configureForRoom:FALSE];
else
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}
void main_view_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomState newState) {
PhoneMainView *view = PhoneMainView.instance;
switch (newState) {
case LinphoneChatRoomStateCreated: {
LOGI(@"Chat room [%p] created on server.", cr);
linphone_chat_room_remove_callbacks(cr, linphone_chat_room_get_current_callbacks(cr));
[view goToChatRoom:cr];
if (!IPAD)
break;
if (PhoneMainView.instance.currentView != ChatsListView.compositeViewDescription && PhoneMainView.instance.currentView != ChatConversationView.compositeViewDescription)
break;
ChatsListView *mainView = VIEW(ChatsListView);
[mainView.tableController loadData];
[mainView.tableController selectFirstRow];
break;
}
case LinphoneChatRoomStateCreationFailed:
LOGE(@"Chat room [%p] could not be created on server.", cr);
linphone_chat_room_remove_callbacks(cr, linphone_chat_room_get_current_callbacks(cr));
view.waitView.hidden = YES;
[ChatConversationInfoView displayCreationError];
break;
case LinphoneChatRoomStateTerminated:
LOGI(@"Chat room [%p] has been terminated.", cr);
[view goToChatRoom:cr];
break;
default:
break;
}
}
#pragma mark - SMS invite callback
- (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result {
[controller dismissModalViewControllerAnimated:YES];
}
@end