blob: df4b71cc4416dad0efb28d0524ad44639606d814 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/coordinators/browser_coordinator.h"
#import "base/ios/block_types.h"
#import "base/logging.h"
#import "ios/chrome/browser/ui/coordinators/browser_coordinator+internal.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Enum class describing the current coordinator and consumer state.
enum class ActivationState {
DEACTIVATED, // The coordinator has been stopped or has never been started.
// its UIViewController has been fully dismissed.
ACTIVATING, // The coordinator has been started, but its UIViewController
// hasn't finished being presented.
ACTIVATED, // The coordinator has been started and its UIViewController has
// finished being presented.
DEACTIVATING, // The coordinator has been stopped, but its UIViewController
// hasn't finished being dismissed.
};
// Returns the presentation state to use after |current_state|.
ActivationState GetNextActivationState(ActivationState current_state) {
switch (current_state) {
case ActivationState::DEACTIVATED:
return ActivationState::ACTIVATING;
case ActivationState::ACTIVATING:
return ActivationState::ACTIVATED;
case ActivationState::ACTIVATED:
return ActivationState::DEACTIVATING;
case ActivationState::DEACTIVATING:
return ActivationState::DEACTIVATED;
}
}
}
@interface BrowserCoordinator ()
// The coordinator's presentation state.
@property(nonatomic, assign) ActivationState activationState;
// Child coordinators owned by this object.
@property(nonatomic, strong)
NSMutableSet<BrowserCoordinator*>* childCoordinators;
// Parent coordinator of this object, if any.
@property(nonatomic, readwrite, weak) BrowserCoordinator* parentCoordinator;
// Updates |activationState| to the next appropriate value after the in-
// progress transition animation finishes. If there is no animation occurring,
// the state is updated immediately.
- (void)updateActivationStateAfterTransition;
@end
@implementation BrowserCoordinator
@synthesize browser = _browser;
@synthesize dispatcher = _dispatcher;
@synthesize activationState = _activationState;
@synthesize childCoordinators = _childCoordinators;
@synthesize parentCoordinator = _parentCoordinator;
- (instancetype)init {
if (self = [super init]) {
_activationState = ActivationState::DEACTIVATED;
_childCoordinators = [NSMutableSet set];
}
return self;
}
- (void)dealloc {
for (BrowserCoordinator* child in self.children) {
[self removeChildCoordinator:child];
}
}
#pragma mark - Accessors
- (BOOL)isStarted {
return self.activationState == ActivationState::ACTIVATING ||
self.activationState == ActivationState::ACTIVATED;
}
- (void)setActivationState:(ActivationState)state {
if (_activationState == state)
return;
DCHECK_EQ(state, GetNextActivationState(_activationState))
<< "Unexpected activation state. Probably from calling |-stop| while"
<< "ACTIVATING or |-start| while DEACTIVATING.";
_activationState = state;
if (_activationState == ActivationState::DEACTIVATED) {
[self viewControllerWasDeactivated];
} else if (_activationState == ActivationState::ACTIVATED) {
[self viewControllerWasActivated];
}
}
#pragma mark - Public API
- (id)callableDispatcher {
return self.dispatcher;
}
- (void)start {
if (self.started)
return;
self.activationState = ActivationState::ACTIVATING;
[self.parentCoordinator childCoordinatorDidStart:self];
[self updateActivationStateAfterTransition];
}
- (void)stop {
if (!self.started)
return;
[self.parentCoordinator childCoordinatorWillStop:self];
self.activationState = ActivationState::DEACTIVATING;
for (BrowserCoordinator* child in self.children) {
[child stop];
}
[self updateActivationStateAfterTransition];
}
#pragma mark - Private
- (void)updateActivationStateAfterTransition {
DCHECK(self.activationState == ActivationState::ACTIVATING ||
self.activationState == ActivationState::DEACTIVATING);
ActivationState nextState = GetNextActivationState(self.activationState);
id<UIViewControllerTransitionCoordinator> transitionCoordinator =
self.viewController.transitionCoordinator;
if (transitionCoordinator) {
__weak BrowserCoordinator* weakSelf = self;
[transitionCoordinator animateAlongsideTransition:nil
completion:^(id context) {
weakSelf.activationState =
nextState;
}];
} else {
self.activationState = nextState;
}
}
@end
@implementation BrowserCoordinator (Internal)
// Concrete implementations must implement a |viewController| property.
@dynamic viewController;
- (NSSet*)children {
return [self.childCoordinators copy];
}
- (void)addChildCoordinator:(BrowserCoordinator*)childCoordinator {
CHECK([self respondsToSelector:@selector(viewController)])
<< "BrowserCoordinator implementations must provide a viewController "
"property.";
[self.childCoordinators addObject:childCoordinator];
childCoordinator.parentCoordinator = self;
childCoordinator.browser = self.browser;
childCoordinator.dispatcher = self.dispatcher;
[childCoordinator wasAddedToParentCoordinator:self];
}
- (void)removeChildCoordinator:(BrowserCoordinator*)childCoordinator {
if (![self.childCoordinators containsObject:childCoordinator])
return;
// Remove the grand-children first.
for (BrowserCoordinator* grandChild in childCoordinator.children) {
[childCoordinator removeChildCoordinator:grandChild];
}
// Remove the child.
[childCoordinator willBeRemovedFromParentCoordinator];
[self.childCoordinators removeObject:childCoordinator];
childCoordinator.parentCoordinator = nil;
childCoordinator.browser = nil;
}
- (void)wasAddedToParentCoordinator:(BrowserCoordinator*)parentCoordinator {
// Default implementation is a no-op.
}
- (void)willBeRemovedFromParentCoordinator {
// Default implementation is a no-op.
}
- (void)childCoordinatorDidStart:(BrowserCoordinator*)childCoordinator {
// Default implementation is a no-op.
}
- (void)childCoordinatorWillStop:(BrowserCoordinator*)childCoordinator {
// Default implementation is a no-op.
}
- (void)viewControllerWasActivated {
// Default implementation is a no-op.
}
- (void)viewControllerWasDeactivated {
// Default implementation is a no-op.
}
@end