chromium/ios/chrome/browser/bookmarks/ui_bundled/home/bookmark_promo_controller.mm

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/bookmarks/ui_bundled/home/bookmark_promo_controller.h"

#import <memory>

#import "components/bookmarks/common/bookmark_features.h"
#import "components/prefs/pref_service.h"
#import "components/signin/public/base/signin_pref_names.h"
#import "components/signin/public/identity_manager/account_info.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"
#import "components/sync/service/sync_service.h"
#import "ios/chrome/browser/bookmarks/ui_bundled/bookmark_utils_ios.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/signin/model/chrome_account_manager_service_factory.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/ui/authentication/account_settings_presenter.h"
#import "ios/chrome/browser/ui/authentication/cells/signin_promo_view_configurator.h"
#import "ios/chrome/browser/ui/authentication/cells/signin_promo_view_consumer.h"
#import "ios/chrome/browser/ui/authentication/signin_promo_view_mediator.h"

@interface BookmarkPromoController () <SigninPromoViewConsumer,
                                       IdentityManagerObserverBridgeDelegate>

@end

@implementation BookmarkPromoController {
  base::WeakPtr<Browser> _browser;
  std::unique_ptr<signin::IdentityManagerObserverBridge>
      _identityManagerObserverBridge;
  // Mediator to use for the sign-in promo view displayed in the bookmark view.
  SigninPromoViewMediator* _signinPromoViewMediator;
}

- (instancetype)initWithBrowser:(Browser*)browser
                    syncService:(syncer::SyncService*)syncService
                       delegate:(id<BookmarkPromoControllerDelegate>)delegate
                signinPresenter:(id<SigninPresenter>)signinPresenter
       accountSettingsPresenter:
           (id<AccountSettingsPresenter>)accountSettingsPresenter {
  DCHECK(browser);
  self = [super init];
  if (self) {
    _delegate = delegate;
    ChromeBrowserState* browserState =
        browser->GetBrowserState()->GetOriginalChromeBrowserState();
    _browser = browser->AsWeakPtr();
    _identityManagerObserverBridge.reset(
        new signin::IdentityManagerObserverBridge(
            IdentityManagerFactory::GetForBrowserState(browserState), self));
    _signinPromoViewMediator = [[SigninPromoViewMediator alloc]
        initWithAccountManagerService:ChromeAccountManagerServiceFactory::
                                          GetForBrowserState(browserState)
                          authService:AuthenticationServiceFactory::
                                          GetForBrowserState(browserState)
                          prefService:browserState->GetPrefs()
                          syncService:syncService
                          accessPoint:signin_metrics::AccessPoint::
                                          ACCESS_POINT_BOOKMARK_MANAGER
                      signinPresenter:signinPresenter
             accountSettingsPresenter:accountSettingsPresenter];
    _signinPromoViewMediator.consumer = self;
    _signinPromoViewMediator.dataTypeToWaitForInitialSync =
        syncer::DataType::BOOKMARKS;
    [self updateShouldShowSigninPromo];
  }
  return self;
}

- (void)shutdown {
  [_signinPromoViewMediator disconnect];
  _signinPromoViewMediator = nil;
  _browser = nullptr;
  _identityManagerObserverBridge.reset();
}

- (void)hidePromoCell {
  DCHECK(_browser);
  self.shouldShowSigninPromo = NO;
}

- (void)setShouldShowSigninPromo:(BOOL)shouldShowSigninPromo {
  if (_shouldShowSigninPromo != shouldShowSigninPromo) {
    _shouldShowSigninPromo = shouldShowSigninPromo;
    [self.delegate promoStateChanged:shouldShowSigninPromo];
  }
}

- (void)updateShouldShowSigninPromo {
  DCHECK(_browser);
  ChromeBrowserState* browserState =
      _browser->GetBrowserState()->GetOriginalChromeBrowserState();
  AuthenticationService* authenticationService =
      AuthenticationServiceFactory::GetForBrowserState(browserState);
  signin::IdentityManager* identityManager =
      IdentityManagerFactory::GetForBrowserState(browserState);
  syncer::SyncService* syncService =
      SyncServiceFactory::GetForBrowserState(browserState);

  std::optional<SigninPromoAction> signinPromoAction;
  if (!identityManager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
    signinPromoAction = SigninPromoAction::kInstantSignin;
  } else if (identityManager->HasPrimaryAccount(signin::ConsentLevel::kSync)) {
    // TODO(crbug.com/40066949): Simplify once kSync becomes unreachable or is
    // deleted from the codebase. See ConsentLevel::kSync documentation for
    // details.
    // If the user is already syncing, the promo should not be visible.
    self.shouldShowSigninPromo = NO;
    return;
  } else if (!bookmark_utils_ios::IsAccountBookmarkStorageOptedIn(
                 syncService)) {
    if (self.shouldShowSigninPromo &&
        _signinPromoViewMediator.signinPromoAction !=
            SigninPromoAction::kReviewAccountSettings) {
      // The promo was visible with another action. `shouldShowSigninPromo`
      // needs to be toggled first to reflect this change.
      self.shouldShowSigninPromo = NO;
    }
    // The user signed in, but not opted into account bookmarks storage - show
    // review account settings promo.
    signinPromoAction = SigninPromoAction::kReviewAccountSettings;
  } else if (self.signinPromoViewMediator.showSpinner) {
    // The user is opted into syncing bookmarks, but the first sync is not
    // finished yet - keep the promo visible with the same action to show the
    // spinner.
    signinPromoAction = SigninPromoAction::kInstantSignin;
  } else {
    // The user is opted into syncing bookmarks and the first sync is done -
    // hide the promo.
    self.shouldShowSigninPromo = NO;
    return;
  }

  CHECK(signinPromoAction.has_value());
  if (![SigninPromoViewMediator
          shouldDisplaySigninPromoViewWithAccessPoint:
              signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_MANAGER
                                    signinPromoAction:signinPromoAction.value()
                                authenticationService:authenticationService
                                          prefService:browserState
                                                          ->GetPrefs()]) {
    self.shouldShowSigninPromo = NO;
    return;
  }

  _signinPromoViewMediator.signinPromoAction = signinPromoAction.value();
  self.shouldShowSigninPromo = YES;
}

#pragma mark - IdentityManagerObserverBridgeDelegate

// Called when a user changes the syncing state.
- (void)onPrimaryAccountChanged:
    (const signin::PrimaryAccountChangeEvent&)event {
  // The account storage promo is not shown if the user is signed-in, so
  // events with sign-in consent level should be captured and handled.
  [self handlePrimaryAccountChange:event
                      consentLevel:signin::ConsentLevel::kSignin];
}

#pragma mark - SigninPromoViewConsumer

- (void)configureSigninPromoWithConfigurator:
            (SigninPromoViewConfigurator*)configurator
                             identityChanged:(BOOL)identityChanged {
  [self.delegate configureSigninPromoWithConfigurator:configurator
                                      identityChanged:identityChanged];
}

- (void)promoProgressStateDidChange {
  [self updateShouldShowSigninPromo];
}

- (void)signinDidFinish {
  [self updateShouldShowSigninPromo];
}

- (void)signinPromoViewMediatorCloseButtonWasTapped:
    (SigninPromoViewMediator*)mediator {
  [self updateShouldShowSigninPromo];
}

#pragma mark - Private methods

// Handles the given primary account change event for the given consent level.
- (void)handlePrimaryAccountChange:
            (const signin::PrimaryAccountChangeEvent&)event
                      consentLevel:(signin::ConsentLevel)consentLevel {
  switch (event.GetEventTypeFor(consentLevel)) {
    case signin::PrimaryAccountChangeEvent::Type::kSet:
      if (!self.signinPromoViewMediator.showSpinner) {
        self.shouldShowSigninPromo = NO;
      }
      break;
    case signin::PrimaryAccountChangeEvent::Type::kCleared:
      [self updateShouldShowSigninPromo];
      break;
    case signin::PrimaryAccountChangeEvent::Type::kNone:
      break;
  }
}

@end