diff --git a/Classes/ChatRoomViewController.m b/Classes/ChatRoomViewController.m index e6b251115..36c5247c1 100644 --- a/Classes/ChatRoomViewController.m +++ b/Classes/ChatRoomViewController.m @@ -108,7 +108,7 @@ static UICompositeViewDescription *compositeDescription = nil; tabBar:/*@"UIMainBar"*/nil tabBarEnabled:false /*to keep room for chat*/ fullscreen:false - landscapeMode:[LinphoneManager runningOnIpad] + landscapeMode:true portraitMode:true]; } return compositeDescription; diff --git a/Classes/InCallViewController.m b/Classes/InCallViewController.m index 0e983a347..fda5ce633 100644 --- a/Classes/InCallViewController.m +++ b/Classes/InCallViewController.m @@ -130,9 +130,6 @@ static UICompositeViewDescription *compositeDescription = nil; [hideControlsTimer invalidate]; hideControlsTimer = nil; } - if ([[UIDevice currentDevice].systemVersion doubleValue] < 5.0) { - [callTableController viewWillDisappear:animated]; - } if( hiddenVolume ) { [[PhoneMainView instance] setVolumeHidden:FALSE]; @@ -147,10 +144,6 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - if ([[UIDevice currentDevice].systemVersion doubleValue] < 5.0) { - [callTableController viewWillAppear:animated]; - } - // Set observer [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(callUpdateEvent:) @@ -161,11 +154,7 @@ static UICompositeViewDescription *compositeDescription = nil; LinphoneCall* call = linphone_core_get_current_call([LinphoneManager getLc]); LinphoneCallState state = (call != NULL)?linphone_call_get_state(call): 0; [self callUpdate:call state:state animated:FALSE]; - - if ([[UIDevice currentDevice].systemVersion doubleValue] < 5.0) { - [callTableController viewDidAppear:animated]; - } - + // Set windows (warn memory leaks) linphone_core_set_native_video_window_id([LinphoneManager getLc], (unsigned long)videoView); linphone_core_set_native_preview_window_id([LinphoneManager getLc], (unsigned long)videoPreview); @@ -180,11 +169,7 @@ static UICompositeViewDescription *compositeDescription = nil; [[UIApplication sharedApplication] setIdleTimerDisabled:false]; UIDevice *device = [UIDevice currentDevice]; device.proximityMonitoringEnabled = NO; - - if ([[UIDevice currentDevice].systemVersion doubleValue] < 5.0) { - [callTableController viewDidDisappear:animated]; - } - + [[PhoneMainView instance] fullScreen:false]; // Disable tap [singleFingerTap setEnabled:FALSE]; diff --git a/Classes/LinphoneAppDelegate.h b/Classes/LinphoneAppDelegate.h index cc452ce36..1e2af5e9a 100644 --- a/Classes/LinphoneAppDelegate.h +++ b/Classes/LinphoneAppDelegate.h @@ -25,7 +25,6 @@ @interface LinphoneAppDelegate : NSObject { @private - UIWindow *window; UIBackgroundTaskIdentifier bgStartId; BOOL started; int savedMaxCall; diff --git a/Classes/LinphoneAppDelegate.m b/Classes/LinphoneAppDelegate.m index 2f6787c6a..4c4b62052 100644 --- a/Classes/LinphoneAppDelegate.m +++ b/Classes/LinphoneAppDelegate.m @@ -231,6 +231,7 @@ if(!started) { started = TRUE; [self.window makeKeyAndVisible]; + [RootViewManager setupWithPortrait:(PhoneMainView*)self.window.rootViewController]; [[PhoneMainView instance] startUp]; } } diff --git a/Classes/LinphoneUI/UICompositeViewController.m b/Classes/LinphoneUI/UICompositeViewController.m index 3578ea3d8..ac679a3a2 100644 --- a/Classes/LinphoneUI/UICompositeViewController.m +++ b/Classes/LinphoneUI/UICompositeViewController.m @@ -308,7 +308,7 @@ firstResponder = [UICompositeViewController findFirstResponder:controller.view]; } - // [[UIApplication sharedApplication] setStatusBarOrientation:orientation animated:animated]; + [[UIApplication sharedApplication] setStatusBarOrientation:orientation animated:animated]; if(firstResponder) { [firstResponder resignFirstResponder]; [firstResponder becomeFirstResponder]; @@ -343,11 +343,6 @@ } if(remove) { [LinphoneLogger log:LinphoneLoggerLog format:@"Free cached view: %@", key]; - UIViewController *vc = [viewControllerCache objectForKey:key]; - if ([[UIDevice currentDevice].systemVersion doubleValue] >= 5.0) { - [vc viewWillUnload]; - } - [vc viewDidUnload]; [viewControllerCache removeObjectForKey:key]; } } @@ -506,7 +501,7 @@ self.tabBarViewController = newTabBarViewController; // Update rotation - UIInterfaceOrientation correctOrientation = [self getCorrectInterfaceOrientation:[[UIDevice currentDevice] orientation]]; + UIInterfaceOrientation correctOrientation = [self getCorrectInterfaceOrientation:(UIDeviceOrientation)[UIApplication sharedApplication].statusBarOrientation]; if(currentOrientation != correctOrientation) { [UICompositeViewController setOrientation:correctOrientation animated:currentOrientation!=UIDeviceOrientationUnknown]; if( UIInterfaceOrientationIsLandscape(correctOrientation) ){ diff --git a/Classes/PhoneMainView.h b/Classes/PhoneMainView.h index 0586e1619..f6a09756a 100644 --- a/Classes/PhoneMainView.h +++ b/Classes/PhoneMainView.h @@ -23,6 +23,7 @@ #import "LinphoneManager.h" #import "UICompositeViewController.h" +/* These imports are here so that we can import PhoneMainView.h without bothering to import all the rest of the view headers */ #import "AboutViewController.h" #import "FirstLoginViewController.h" #import "IncomingCallViewController.h" @@ -43,10 +44,23 @@ #import "ConsoleViewController.h" #import "ImageViewController.h" + +@class PhoneMainView; + +@interface RootViewManager : NSObject + +@property (nonatomic, retain) PhoneMainView* portraitViewController; +@property (nonatomic, retain) PhoneMainView* rotatingViewController; +@property (nonatomic, retain) NSMutableArray* viewDescriptionStack; + ++(RootViewManager*)instance; ++ (void)setupWithPortrait:(PhoneMainView*)portrait; +- (PhoneMainView*)currentView; + +@end + @interface PhoneMainView : UIViewController { @private - int loadCount; - NSMutableArray *viewStack; NSMutableArray *inhibitedEvents; NSTimer *batteryTimer; } @@ -54,6 +68,7 @@ @property (nonatomic, retain) IBOutlet UIView *statusBarBG; @property (nonatomic, retain) IBOutlet UICompositeViewController *mainViewController; +@property (nonatomic, retain) NSString* name; @property (readonly) UICompositeViewDescription *currentView; @property (readonly, retain) MPVolumeView* volumeView; diff --git a/Classes/PhoneMainView.m b/Classes/PhoneMainView.m index 46979750d..d5b78c37e 100644 --- a/Classes/PhoneMainView.m +++ b/Classes/PhoneMainView.m @@ -25,7 +25,83 @@ #import "Utils.h" #import "DTActionSheet.h" -static PhoneMainView* phoneMainViewInstance=nil; + +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] autorelease]; + + 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 { + PhoneMainView* newMainView = description.landscapeMode ? self.rotatingViewController : self.portraitViewController; + + if( [LinphoneManager runningOnIpad] ) return currentViewController; + + if( newMainView != currentViewController ) + { + PhoneMainView* previousMainView = currentViewController; + UIInterfaceOrientation nextViewOrientation = newMainView.interfaceOrientation; + UIInterfaceOrientation previousOrientation = currentViewController.interfaceOrientation; + + Linphone_err(@"Changing rootViewController: %@ -> %@", currentViewController.name, newMainView.name); + currentViewController = newMainView; + LinphoneAppDelegate* delegate = (LinphoneAppDelegate*)[UIApplication sharedApplication].delegate; + + [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) { + }]; + } + return currentViewController; +} + +@end @implementation PhoneMainView @@ -38,11 +114,7 @@ static PhoneMainView* phoneMainViewInstance=nil; #pragma mark - Lifecycle Functions - (void)initPhoneMainView { - assert (!phoneMainViewInstance); - phoneMainViewInstance = self; currentView = nil; - viewStack = [[NSMutableArray alloc] init]; - loadCount = 0; // For avoiding IOS 4 bug inhibitedEvents = [[NSMutableArray alloc] init]; } @@ -75,7 +147,6 @@ static PhoneMainView* phoneMainViewInstance=nil; [mainViewController release]; [inhibitedEvents release]; - [viewStack release]; [super dealloc]; } @@ -84,10 +155,6 @@ static PhoneMainView* phoneMainViewInstance=nil; #pragma mark - ViewController Functions - (void)viewDidLoad { - // Avoid IOS 4 bug - if(loadCount++ > 0) - return; - [super viewDidLoad]; volumeView = [[MPVolumeView alloc] initWithFrame: CGRectMake(-100,-100,16,16)]; @@ -173,9 +240,6 @@ static PhoneMainView* phoneMainViewInstance=nil; - (void)viewDidUnload { [super viewDidUnload]; - - // Avoid IOS 4 bug - loadCount--; } - (void)setVolumeHidden:(BOOL)hidden { @@ -194,15 +258,11 @@ static PhoneMainView* phoneMainViewInstance=nil; - (NSUInteger)supportedInterfaceOrientations { - static NSUInteger supported = -1; - if (supported == -1) { - if( [LinphoneManager runningOnIpad ] ) - supported = UIInterfaceOrientationMaskAll; - else - supported = UIInterfaceOrientationMaskPortrait; + if( [LinphoneManager runningOnIpad ] || [mainViewController currentViewSupportsLandscape] ) + return UIInterfaceOrientationMaskAll; + else { + return UIInterfaceOrientationMaskPortrait; } - - return supported; } - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { @@ -227,7 +287,7 @@ static PhoneMainView* phoneMainViewInstance=nil; - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; - [mainViewController clearCache:viewStack]; + [mainViewController clearCache:[RootViewManager instance].viewDescriptionStack]; } #pragma mark - Event Functions @@ -451,7 +511,7 @@ static PhoneMainView* phoneMainViewInstance=nil; } + (PhoneMainView *) instance { - return phoneMainViewInstance; + return [[RootViewManager instance] currentView]; } - (void) showTabBar:(BOOL)show { @@ -462,18 +522,11 @@ static PhoneMainView* phoneMainViewInstance=nil; [mainViewController setStateBarHidden:!show]; } -+ (BOOL)isDarkBackgroundView:(UICompositeViewDescription*)view { - return ( [view equal:[DialerViewController compositeViewDescription]] || - [view equal:[IncomingCallViewController compositeViewDescription]] || - [view equal:[InCallViewController compositeViewDescription]] || - [view equal:[WizardViewController compositeViewDescription]]); -} - - (void)updateStatusBar:(UICompositeViewDescription*)to_view { #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 // 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) && ![PhoneMainView isDarkBackgroundView:to_view]; + BOOL toLightStatus = (to_view != NULL) && ![to_view darkBackground]; if( !toLightStatus ) { // black bg: white text on black background [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent]; @@ -504,7 +557,8 @@ static PhoneMainView* phoneMainViewInstance=nil; - (UIViewController*)changeCurrentView:(UICompositeViewDescription*)view push:(BOOL)push { BOOL force = push; - if(!push) { + NSMutableArray* viewStack = [RootViewManager instance].viewDescriptionStack; + if(!push ) { force = [viewStack count] > 1; [viewStack removeAllObjects]; } @@ -514,27 +568,32 @@ static PhoneMainView* phoneMainViewInstance=nil; - (UIViewController*)_changeCurrentView:(UICompositeViewDescription*)view transition:(CATransition*)transition force:(BOOL)force { [LinphoneLogger logc:LinphoneLoggerLog format:"PhoneMainView: Change current view to %@", [view name]]; - - if(force || ![view equal: currentView]) { + + PhoneMainView* vc = [[RootViewManager instance] setViewControllerForDescription:view]; + + if(force || ![view equal:vc.currentView] || vc != self) { if(transition == nil) - transition = [PhoneMainView getTransition:currentView new:view]; + transition = [PhoneMainView getTransition:vc.currentView new:view]; if ([[LinphoneManager instance] lpConfigBoolForKey:@"animations_preference"] == true) { - [mainViewController setViewTransition:transition]; + [vc.mainViewController setViewTransition:transition]; } else { - [mainViewController setViewTransition:nil]; + [vc.mainViewController setViewTransition:nil]; } - [self updateStatusBar:view]; - [mainViewController changeView:view]; - currentView = view; + [vc updateStatusBar:view]; + [vc.mainViewController changeView:view]; + vc->currentView = view; } + + //[[RootViewManager instance] setViewControllerForDescription:view]; - NSDictionary* mdict = [NSMutableDictionary dictionaryWithObject:currentView forKey:@"view"]; + NSDictionary* mdict = [NSMutableDictionary dictionaryWithObject:vc->currentView forKey:@"view"]; [[NSNotificationCenter defaultCenter] postNotificationName:kLinphoneMainViewChange object:self userInfo:mdict]; - return [mainViewController getCurrentViewController]; + return [vc->mainViewController getCurrentViewController]; } - (void)popToView:(UICompositeViewDescription*)view { + NSMutableArray* viewStack = [RootViewManager instance].viewDescriptionStack; while([viewStack count] > 1 && ![[viewStack lastObject] equal:view]) { [viewStack removeLastObject]; } @@ -543,7 +602,8 @@ static PhoneMainView* phoneMainViewInstance=nil; - (UICompositeViewDescription *)firstView { UICompositeViewDescription *view = nil; - if([viewStack count]) { + NSArray* viewStack = [RootViewManager instance].viewDescriptionStack; + if([viewStack count]) { view = [viewStack objectAtIndex:0]; } return view; @@ -551,7 +611,8 @@ static PhoneMainView* phoneMainViewInstance=nil; - (UIViewController*)popCurrentView { [LinphoneLogger logc:LinphoneLoggerLog format:"PhoneMainView: Pop view"]; - if([viewStack count] > 1) { + NSMutableArray* viewStack = [RootViewManager instance].viewDescriptionStack; + if([viewStack count] > 1) { [viewStack removeLastObject]; [self _changeCurrentView:[viewStack lastObject] transition:[PhoneMainView getBackwardTransition] force:TRUE]; return [mainViewController getCurrentViewController]; diff --git a/UI.md b/UI.md new file mode 100644 index 000000000..e048dafdc --- /dev/null +++ b/UI.md @@ -0,0 +1,34 @@ + +Quick UI reference for Linphone iOS: + +- The app is contained in a window, which resides in the MainStoryboard file. +- The delegate is set to LinphoneAppDelegate in main.m, in the UIApplicationMain() by passing its class +- Basic layout: + + +MainStoryboard + | + | (rootViewController) + | + PhoneMainView ---> view #--> app background + | | + | #--> statusbar background + | + | (mainViewController) + | + UICompositeViewController : TPMultilayout + | + #---> view #--> stateBar + | + #--> contentView + | + #--> tabBar + + +When the app is started, the phoneMainView gets asked to transition to the Dialer view or the Wizard view. +PhoneMainView exposes the -changeCurrentView: method, which will setup its +Any Linphone view is actually presented in the UICompositeViewController, with or without a stateBar and tabBar. + +The UICompositeViewController consists of 3 areas laid out vertically. From top to bottom: StateBar, Content and TabBar. +The TabBar is usually the UIMainBar, which is used as a navigation controller: clicking on each of the buttons will trigger +a transition to another "view". \ No newline at end of file