chromium/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/inactive_tabs_button_mediator.mm

// Copyright 2023 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/ui/tab_switcher/tab_grid/inactive_tabs/inactive_tabs_button_mediator.h"

#import "base/memory/raw_ptr.h"
#import "base/notreached.h"
#import "base/scoped_observation.h"
#import "components/prefs/ios/pref_observer_bridge.h"
#import "components/prefs/pref_change_registrar.h"
#import "components/prefs/pref_service.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list_observer_bridge.h"
#import "ios/chrome/browser/tabs/model/inactive_tabs/features.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/inactive_tabs_info_consumer.h"

using ScopedWebStateListObservation =
    base::ScopedObservation<WebStateList, WebStateListObserver>;

@interface InactiveTabsButtonMediator () <PrefObserverDelegate,
                                          WebStateListObserving>
@end

@implementation InactiveTabsButtonMediator {
  // The UI consumer to which updates are made.
  __weak id<InactiveTabsInfoConsumer> _consumer;
  // The list of inactive tabs.
  raw_ptr<WebStateList> _webStateList;
  // Observers of _webStateList.
  std::unique_ptr<WebStateListObserverBridge> _webStateListObserverBridge;
  std::unique_ptr<ScopedWebStateListObservation> _scopedWebStateListObservation;
  // Preference service from the application context.
  raw_ptr<PrefService> _prefService;
  // Pref observer to track changes to prefs.
  std::unique_ptr<PrefObserverBridge> _prefObserverBridge;
  // Registrar for pref changes notifications.
  PrefChangeRegistrar _prefChangeRegistrar;
}

- (instancetype)initWithConsumer:(id<InactiveTabsInfoConsumer>)consumer
                    webStateList:(WebStateList*)webStateList
                     prefService:(PrefService*)prefService {
  CHECK(IsInactiveTabsAvailable());
  // TODO(crbug.com/40923937): Reinstate this CHECK once
  // InactiveTabsButtonMediator is not created when not needed (for example when
  // a policy disables the regular tab grid).
  // CHECK(consumer);
  CHECK(webStateList);
  CHECK(prefService);
  self = [super init];
  if (self) {
    _consumer = consumer;
    _webStateList = webStateList;

    // Observe the web state list.
    _webStateListObserverBridge =
        std::make_unique<WebStateListObserverBridge>(self);
    _scopedWebStateListObservation =
        std::make_unique<ScopedWebStateListObservation>(
            _webStateListObserverBridge.get());
    _scopedWebStateListObservation->Observe(_webStateList);

    // Observe the preferences for changes to Inactive Tabs settings.
    _prefService = prefService;
    _prefChangeRegistrar.Init(_prefService);
    _prefObserverBridge = std::make_unique<PrefObserverBridge>(self);
    // Register to observe any changes on pref backed values displayed by the
    // screen.
    _prefObserverBridge->ObserveChangesForPreference(
        prefs::kInactiveTabsTimeThreshold, &_prefChangeRegistrar);

    // Push the info to the consumer.
    [_consumer updateInactiveTabsCount:_webStateList->count()];
    NSInteger daysThreshold = InactiveTabsTimeThreshold().InDays();
    [_consumer updateInactiveTabsDaysThreshold:daysThreshold];
  }
  return self;
}

- (void)disconnect {
  _consumer = nil;
  _scopedWebStateListObservation.reset();
  _webStateListObserverBridge.reset();
  _webStateList = nullptr;
  _prefChangeRegistrar.RemoveAll();
  _prefObserverBridge.reset();
  _prefService = nullptr;
}

#pragma mark - PrefObserverDelegate

- (void)onPreferenceChanged:(const std::string&)preferenceName {
  if (preferenceName == prefs::kInactiveTabsTimeThreshold) {
    NSInteger daysThreshold =
        _prefService->GetInteger(prefs::kInactiveTabsTimeThreshold);
    [_consumer updateInactiveTabsDaysThreshold:daysThreshold];
  }
}

#pragma mark - WebStateListObserving

- (void)willChangeWebStateList:(WebStateList*)webStateList
                        change:(const WebStateListChangeDetach&)detachChange
                        status:(const WebStateListStatus&)status {
  // Do nothing. Updating the consumer with the new count will be handled in
  // didChangeWebStateList:change:status: with kDetach.
}

- (void)didChangeWebStateList:(WebStateList*)webStateList
                       change:(const WebStateListChange&)change
                       status:(const WebStateListStatus&)status {
  DCHECK_EQ(_webStateList, webStateList);
  if (_webStateList->IsBatchInProgress()) {
    // Consumer will be updated at the end of the batch.
    return;
  }

  switch (change.type()) {
    case WebStateListChange::Type::kStatusOnly:
      // Do nothing when the status in WebStateList is updated.
      break;
    case WebStateListChange::Type::kDetach:
      [_consumer updateInactiveTabsCount:_webStateList->count()];
      break;
    case WebStateListChange::Type::kMove:
    case WebStateListChange::Type::kReplace:
    case WebStateListChange::Type::kInsert:
    case WebStateListChange::Type::kGroupCreate:
    case WebStateListChange::Type::kGroupVisualDataUpdate:
    case WebStateListChange::Type::kGroupMove:
    case WebStateListChange::Type::kGroupDelete:
      NOTREACHED();
  }
}

- (void)webStateListWillBeginBatchOperation:(WebStateList*)webStateList {
  // No-op. This is called when all inactive tabs are closed at once.
}

- (void)webStateListBatchOperationEnded:(WebStateList*)webStateList {
  DCHECK_EQ(_webStateList, webStateList);
  [_consumer updateInactiveTabsCount:_webStateList->count()];
}

- (void)webStateListDestroyed:(WebStateList*)webStateList {
  DCHECK_EQ(webStateList, _webStateList);
  _scopedWebStateListObservation.reset();
  _webStateList = nullptr;
}

@end