chromium/ash/system/network/network_detailed_network_view_impl.cc

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

#include "ash/system/network/network_detailed_network_view_impl.h"

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/ash_view_ids.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/rounded_container.h"
#include "ash/style/typography.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/network/network_detailed_view.h"
#include "ash/system/network/network_list_mobile_header_view.h"
#include "ash/system/network/network_list_network_item_view.h"
#include "ash/system/network/network_list_tether_hosts_header_view.h"
#include "ash/system/network/network_list_wifi_header_view.h"
#include "ash/system/network/network_utils.h"
#include "ash/system/network/tray_network_state_model.h"
#include "ash/system/tray/detailed_view_delegate.h"
#include "ash/system/tray/tray_popup_utils.h"
#include "base/notreached.h"
#include "chromeos/services/network_config/public/mojom/network_types.mojom-shared.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"

namespace ash {
namespace {
using chromeos::network_config::mojom::InhibitReason;

constexpr auto kMainContainerMargins = gfx::Insets::TLBR(2, 0, 0, 0);
constexpr auto kTopContainerBorder = gfx::Insets::TLBR(4, 0, 4, 4);
constexpr auto kBetweenContainerMargins = gfx::Insets::TLBR(6, 0, 0, 0);

// The following getter methods should only be used for
// `NetworkType::kWiFi`, `NetworkType::kTether`, `NetworkType::kMobile`, or
// `NetworkType::kCellular` types otherwise a crash will occur.
std::u16string GetLabelForConfigureNetworkEntry(NetworkType type) {
  switch (type) {
    case NetworkType::kWiFi:
      return l10n_util::GetStringUTF16(
          IDS_ASH_QUICK_SETTINGS_JOIN_WIFI_NETWORK);
    case NetworkType::kTether:
      if (features::IsInstantHotspotRebrandEnabled()) {
        return l10n_util::GetStringUTF16(
            IDS_ASH_QUICK_SETTINGS_SET_UP_YOUR_DEVICE);
      }
      [[fallthrough]];
    case NetworkType::kCellular:
      [[fallthrough]];
    case NetworkType::kMobile:
      return l10n_util::GetStringUTF16(IDS_ASH_QUICK_SETTINGS_ADD_ESIM);
    default:
      NOTREACHED();
  }
}

std::optional<std::u16string> GetTooltipForConfigureNetworkEntry(
    NetworkType type) {
  switch (type) {
    case NetworkType::kWiFi:
      return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_OTHER_WIFI);
    case NetworkType::kTether:
      if (features::IsInstantHotspotRebrandEnabled()) {
        return std::nullopt;
      }
      [[fallthrough]];
    case NetworkType::kCellular:
      [[fallthrough]];
    case NetworkType::kMobile:
      return l10n_util::GetStringUTF16(GetAddESimTooltipMessageId());
    default:
      NOTREACHED();
  }
}

int GetViewIDForConfigureNetworkEntry(NetworkType type) {
  switch (type) {
    case NetworkType::kWiFi:
      return VIEW_ID_JOIN_WIFI_NETWORK_ENTRY;
    case NetworkType::kTether:
      if (features::IsInstantHotspotRebrandEnabled()) {
        return VIEW_ID_OPEN_CROSS_DEVICE_SETTINGS;
      }
      [[fallthrough]];
    case NetworkType::kCellular:
      [[fallthrough]];
    case NetworkType::kMobile:
      return VIEW_ID_ADD_ESIM_ENTRY;
    default:
      NOTREACHED();
  }
}
}  // namespace

NetworkDetailedNetworkViewImpl::NetworkDetailedNetworkViewImpl(
    DetailedViewDelegate* detailed_view_delegate,
    NetworkDetailedNetworkView::Delegate* delegate)
    : NetworkDetailedView(detailed_view_delegate,
                          delegate,
                          NetworkDetailedViewListType::LIST_TYPE_NETWORK),
      NetworkDetailedNetworkView(delegate) {}

NetworkDetailedNetworkViewImpl::~NetworkDetailedNetworkViewImpl() = default;

void NetworkDetailedNetworkViewImpl::NotifyNetworkListChanged() {
  scroll_content()->InvalidateLayout();
  DeprecatedLayoutImmediately();

  if (!settings_button()) {
    return;
  }

  if (Shell::Get()->session_controller()->login_status() ==
      LoginStatus::NOT_LOGGED_IN) {
    // When not logged in, only enable the settings button if there is a
    // default (i.e. connected or connecting) network to show settings for.
    settings_button()->SetEnabled(model()->default_network());
  } else {
    // Otherwise, enable if showing settings is allowed. There are situations
    // (supervised user creation flow) when the session is started but UI flow
    // continues within login UI, i.e., no browser window is yet available.
    settings_button()->SetEnabled(
        Shell::Get()->session_controller()->ShouldEnableSettings());
  }
}

views::View* NetworkDetailedNetworkViewImpl::GetAsView() {
  return this;
}

NetworkListNetworkItemView* NetworkDetailedNetworkViewImpl::AddNetworkListItem(
    NetworkType type) {
  return GetNetworkList(type)->AddChildView(
      std::make_unique<NetworkListNetworkItemView>(/*listener=*/this));
}

HoverHighlightView* NetworkDetailedNetworkViewImpl::AddConfigureNetworkEntry(
    NetworkType type) {
  CHECK(type == NetworkType::kWiFi || type == NetworkType::kMobile ||
        type == NetworkType::kCellular ||
        (features::IsInstantHotspotRebrandEnabled() &&
         type == NetworkType::kTether));
  HoverHighlightView* entry = GetNetworkList(type)->AddChildView(
      std::make_unique<HoverHighlightView>(/*listener=*/this));
  entry->SetID(GetViewIDForConfigureNetworkEntry(type));

  auto tooltip_text = GetTooltipForConfigureNetworkEntry(type);
  if (tooltip_text.has_value()) {
    entry->SetTooltipText(tooltip_text.value());
  }

  auto image_view = std::make_unique<views::ImageView>();
  image_view->SetImage(ui::ImageModel::FromVectorIcon(
      kSystemMenuPlusIcon, cros_tokens::kCrosSysPrimary));
  entry->AddViewAndLabel(std::move(image_view),
                         GetLabelForConfigureNetworkEntry(type));
  views::Label* label = entry->text_label();
  label->SetEnabledColorId(cros_tokens::kCrosSysPrimary);
  TypographyProvider::Get()->StyleLabel(ash::TypographyToken::kCrosButton2,
                                        *label);

  return entry;
}

NetworkListWifiHeaderView*
NetworkDetailedNetworkViewImpl::AddWifiSectionHeader() {
  if (!wifi_top_container_) {
    wifi_top_container_ =
        scroll_content()->AddChildView(std::make_unique<RoundedContainer>(
            RoundedContainer::Behavior::kTopRounded));
    wifi_top_container_->SetBorderInsets(kTopContainerBorder);
    wifi_top_container_->SetProperty(views::kMarginsKey,
                                     kBetweenContainerMargins);
  }
  return wifi_top_container_->AddChildView(
      std::make_unique<NetworkListWifiHeaderView>(/*delegate=*/this));
}

NetworkListMobileHeaderView*
NetworkDetailedNetworkViewImpl::AddMobileSectionHeader() {
  if (!mobile_top_container_) {
    mobile_top_container_ =
        scroll_content()->AddChildView(std::make_unique<RoundedContainer>(
            RoundedContainer::Behavior::kTopRounded));
    mobile_top_container_->SetBorderInsets(kTopContainerBorder);
  }
  return mobile_top_container_->AddChildView(
      std::make_unique<NetworkListMobileHeaderView>(/*delegate=*/this));
}

NetworkListTetherHostsHeaderView*
NetworkDetailedNetworkViewImpl::AddTetherHostsSectionHeader(
    NetworkListTetherHostsHeaderView::OnExpandedStateToggle callback) {
  DCHECK(features::IsInstantHotspotRebrandEnabled());
  NetworkListTetherHostsHeaderView* header_view =
      scroll_content()->AddChildView(
          std::make_unique<NetworkListTetherHostsHeaderView>(
              std::move(callback)));
  header_view->SetBorderInsets(kTopContainerBorder);
  header_view->SetProperty(views::kMarginsKey, kBetweenContainerMargins);
  return header_view;
}

views::View* NetworkDetailedNetworkViewImpl::GetNetworkList(NetworkType type) {
  switch (type) {
    case NetworkType::kWiFi:
      if (!wifi_network_list_view_) {
        wifi_network_list_view_ =
            scroll_content()->AddChildView(std::make_unique<RoundedContainer>(
                RoundedContainer::Behavior::kBottomRounded));

        // Add a small empty space, like a separator, between the containers.
        wifi_network_list_view_->SetProperty(views::kMarginsKey,
                                             kMainContainerMargins);
      }
      return wifi_network_list_view_;
    case NetworkType::kMobile:
    case NetworkType::kTether:
      if (features::IsInstantHotspotRebrandEnabled()) {
        if (!tether_hosts_network_list_view_) {
          tether_hosts_network_list_view_ =
              scroll_content()->AddChildView(std::make_unique<RoundedContainer>(
                  RoundedContainer::Behavior::kBottomRounded));

          // Add a small empty space, like a separator, between the containers.
          tether_hosts_network_list_view_->SetProperty(views::kMarginsKey,
                                                       kMainContainerMargins);
        }
        return tether_hosts_network_list_view_;
      }
      [[fallthrough]];
    case NetworkType::kCellular:
      if (!mobile_network_list_view_) {
        mobile_network_list_view_ =
            scroll_content()->AddChildView(std::make_unique<RoundedContainer>(
                RoundedContainer::Behavior::kBottomRounded));

        // Add a small empty space, like a separator, between the containers.
        mobile_network_list_view_->SetProperty(views::kMarginsKey,
                                               kMainContainerMargins);
      }
      return mobile_network_list_view_;
    case NetworkType::kEthernet:
      if (!first_list_view_) {
        first_list_view_ = scroll_content()->AddChildView(
            std::make_unique<RoundedContainer>());
        first_list_view_->SetProperty(views::kMarginsKey,
                                      gfx::Insets::TLBR(0, 0, 6, 0));
      }
      return first_list_view_;
    case NetworkType::kAll:
    default:
      return scroll_content();
  }
  NOTREACHED();
}

void NetworkDetailedNetworkViewImpl::ReorderFirstListView(size_t index) {
  if (first_list_view_) {
    scroll_content()->ReorderChildView(first_list_view_, index);
  }
}

void NetworkDetailedNetworkViewImpl::ReorderNetworkTopContainer(size_t index) {
  if (wifi_top_container_) {
    scroll_content()->ReorderChildView(wifi_top_container_, index);
  }
}

void NetworkDetailedNetworkViewImpl::ReorderNetworkListView(size_t index) {
  if (wifi_network_list_view_) {
    scroll_content()->ReorderChildView(wifi_network_list_view_, index);
  }
}

void NetworkDetailedNetworkViewImpl::ReorderMobileTopContainer(size_t index) {
  if (mobile_top_container_) {
    scroll_content()->ReorderChildView(mobile_top_container_, index);
  }
}

void NetworkDetailedNetworkViewImpl::ReorderMobileListView(size_t index) {
  if (mobile_network_list_view_) {
    scroll_content()->ReorderChildView(mobile_network_list_view_, index);
  }
}

void NetworkDetailedNetworkViewImpl::ReorderTetherHostsListView(size_t index) {
  DCHECK(base::FeatureList::IsEnabled(features::kInstantHotspotRebrand));
  if (tether_hosts_network_list_view_) {
    scroll_content()->ReorderChildView(tether_hosts_network_list_view_, index);
  }
}

void NetworkDetailedNetworkViewImpl::MaybeRemoveFirstListView() {
  if (first_list_view_ && first_list_view_->children().empty()) {
    scroll_content()->RemoveChildViewT(first_list_view_.get());
    first_list_view_ = nullptr;
  }
}

void NetworkDetailedNetworkViewImpl::UpdateWifiStatus(bool enabled) {
  if (wifi_top_container_) {
    wifi_top_container_->SetBehavior(
        enabled ? RoundedContainer::Behavior::kTopRounded
                : RoundedContainer::Behavior::kAllRounded);
  }
  if (wifi_network_list_view_) {
    wifi_network_list_view_->SetVisible(enabled);
  }
}

void NetworkDetailedNetworkViewImpl::UpdateMobileStatus(bool enabled) {
  if (mobile_top_container_) {
    mobile_top_container_->SetBehavior(
        enabled ? RoundedContainer::Behavior::kTopRounded
                : RoundedContainer::Behavior::kAllRounded);
  }
  if (mobile_network_list_view_) {
    mobile_network_list_view_->SetVisible(enabled);
  }
}

void NetworkDetailedNetworkViewImpl::UpdateTetherHostsStatus(bool enabled) {
  if (tether_hosts_network_list_view_) {
    tether_hosts_network_list_view_->SetVisible(enabled);
  }
}

void NetworkDetailedNetworkViewImpl::ScrollToPosition(int position) {
  if (GetScrollPosition() == position) {
    return;
  }
  scroller()->ScrollToPosition(scroller()->vertical_scroll_bar(), position);
}

int NetworkDetailedNetworkViewImpl::GetScrollPosition() {
  return scroller()->GetVisibleRect().y();
}

void NetworkDetailedNetworkViewImpl::OnMobileToggleClicked(bool new_state) {
  NetworkDetailedNetworkView::delegate()->OnMobileToggleClicked(new_state);
}

void NetworkDetailedNetworkViewImpl::OnWifiToggleClicked(bool new_state) {
  NetworkDetailedNetworkView::delegate()->OnWifiToggleClicked(new_state);
}

void NetworkDetailedNetworkViewImpl::UpdateScanningBarVisibility(bool visible) {
  ShowProgress(-1, visible);
}

BEGIN_METADATA(NetworkDetailedNetworkViewImpl)
END_METADATA

}  // namespace ash