mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-01-17 11:08:06 +00:00
Dynamically change the root view controller, so that when we transition from a portrait-only to a landscape-friendly view.
This allows to support both orientations for the iPhone. We now use a RootViewController for this, instead of relying only on the PhoneMainView. The root view controller is in charge of swapping the portrait-only and the landscape PhoneMainView when needed. It also inherits the view stack management, since we now have 2 phone main views. This is a bit convoluted, but necessary to handle the ChatRoom and the InCall views for the iPhone
This commit is contained in:
parent
8595c6a6fa
commit
dc04af32b4
8 changed files with 162 additions and 72 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
@interface LinphoneAppDelegate : NSObject <UIApplicationDelegate,UIAlertViewDelegate> {
|
||||
@private
|
||||
UIWindow *window;
|
||||
UIBackgroundTaskIdentifier bgStartId;
|
||||
BOOL started;
|
||||
int savedMaxCall;
|
||||
|
|
|
|||
|
|
@ -231,6 +231,7 @@
|
|||
if(!started) {
|
||||
started = TRUE;
|
||||
[self.window makeKeyAndVisible];
|
||||
[RootViewManager setupWithPortrait:(PhoneMainView*)self.window.rootViewController];
|
||||
[[PhoneMainView instance] startUp];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) ){
|
||||
|
|
|
|||
|
|
@ -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<IncomingCallViewDelegate> {
|
||||
@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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
34
UI.md
Normal file
34
UI.md
Normal file
|
|
@ -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".
|
||||
Loading…
Add table
Reference in a new issue