bluetooth_detailed_view_impl.cc 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/system/bluetooth/bluetooth_detailed_view_impl.h"
  5. #include <memory>
  6. #include "ash/constants/ash_features.h"
  7. #include "ash/public/cpp/system_tray_client.h"
  8. #include "ash/resources/vector_icons/vector_icons.h"
  9. #include "ash/shell.h"
  10. #include "ash/strings/grit/ash_strings.h"
  11. #include "ash/style/ash_color_provider.h"
  12. #include "ash/style/icon_button.h"
  13. #include "ash/system/bluetooth/bluetooth_device_list_item_view.h"
  14. #include "ash/system/bluetooth/bluetooth_disabled_detailed_view.h"
  15. #include "ash/system/model/system_tray_model.h"
  16. #include "ash/system/tray/detailed_view_delegate.h"
  17. #include "ash/system/tray/hover_highlight_view.h"
  18. #include "ash/system/tray/tray_popup_utils.h"
  19. #include "ash/system/tray/tray_toggle_button.h"
  20. #include "ash/system/tray/tri_view.h"
  21. #include "base/check.h"
  22. #include "base/memory/ptr_util.h"
  23. #include "device/bluetooth/chromeos/bluetooth_utils.h"
  24. #include "ui/base/l10n/l10n_util.h"
  25. #include "ui/gfx/geometry/insets.h"
  26. #include "ui/gfx/paint_vector_icon.h"
  27. #include "ui/views/controls/button/button.h"
  28. #include "ui/views/controls/image_view.h"
  29. #include "ui/views/controls/label.h"
  30. #include "ui/views/controls/scroll_view.h"
  31. #include "ui/views/layout/box_layout.h"
  32. #include "ui/views/view.h"
  33. namespace ash {
  34. BluetoothDetailedViewImpl::BluetoothDetailedViewImpl(
  35. DetailedViewDelegate* detailed_view_delegate,
  36. BluetoothDetailedView::Delegate* delegate)
  37. : BluetoothDetailedView(delegate),
  38. TrayDetailedView(detailed_view_delegate) {
  39. DCHECK(ash::features::IsBluetoothRevampEnabled());
  40. CreateTitleRow(IDS_ASH_STATUS_TRAY_BLUETOOTH);
  41. CreateTitleRowButtons();
  42. CreateScrollableList();
  43. CreateDisabledView();
  44. CreatePairNewDeviceView();
  45. UpdateBluetoothEnabledState(/*enabled=*/false);
  46. device::RecordUiSurfaceDisplayed(
  47. device::BluetoothUiSurface::kBluetoothQuickSettings);
  48. }
  49. BluetoothDetailedViewImpl::~BluetoothDetailedViewImpl() = default;
  50. views::View* BluetoothDetailedViewImpl::GetAsView() {
  51. return this;
  52. }
  53. void BluetoothDetailedViewImpl::UpdateBluetoothEnabledState(bool enabled) {
  54. disabled_view_->SetVisible(!enabled);
  55. pair_new_device_view_->SetVisible(enabled);
  56. scroller()->SetVisible(enabled);
  57. const std::u16string toggle_tooltip =
  58. enabled ? l10n_util::GetStringUTF16(
  59. IDS_ASH_STATUS_TRAY_BLUETOOTH_ENABLED_TOOLTIP)
  60. : l10n_util::GetStringUTF16(
  61. IDS_ASH_STATUS_TRAY_BLUETOOTH_DISABLED_TOOLTIP);
  62. toggle_button_->SetTooltipText(l10n_util::GetStringFUTF16(
  63. IDS_ASH_STATUS_TRAY_BLUETOOTH_TOGGLE_TOOLTIP, toggle_tooltip));
  64. // The toggle should already have animated to |enabled| unless it has become
  65. // out of sync with the Bluetooth state, so just set it to the correct state.
  66. if (toggle_button_->GetIsOn() != enabled)
  67. toggle_button_->SetIsOn(enabled);
  68. Layout();
  69. }
  70. BluetoothDeviceListItemView* BluetoothDetailedViewImpl::AddDeviceListItem() {
  71. return scroll_content()->AddChildView(
  72. new BluetoothDeviceListItemView(/*listener=*/this));
  73. }
  74. ash::TriView* BluetoothDetailedViewImpl::AddDeviceListSubHeader(
  75. const gfx::VectorIcon& icon,
  76. int text_id) {
  77. return AddScrollListSubHeader(icon, text_id);
  78. }
  79. void BluetoothDetailedViewImpl::NotifyDeviceListChanged() {
  80. scroll_content()->InvalidateLayout();
  81. Layout();
  82. }
  83. views::View* BluetoothDetailedViewImpl::device_list() {
  84. return scroll_content();
  85. }
  86. void BluetoothDetailedViewImpl::HandleViewClicked(views::View* view) {
  87. // We only handle clicks on the "pair new device" view and on the individual
  88. // device views. When |view| is a child of |pair_new_device_view_| we know the
  89. // "pair new device" button was clicked, otherwise it must have been an
  90. // individual device view.
  91. if (pair_new_device_view_->GetIndexOf(view).has_value()) {
  92. delegate()->OnPairNewDeviceRequested();
  93. return;
  94. }
  95. delegate()->OnDeviceListItemSelected(
  96. static_cast<BluetoothDeviceListItemView*>(view)->device_properties());
  97. }
  98. const char* BluetoothDetailedViewImpl::GetClassName() const {
  99. return "BluetoothDetailedViewImpl";
  100. }
  101. void BluetoothDetailedViewImpl::CreateDisabledView() {
  102. DCHECK(!disabled_view_);
  103. // Check that the views::ScrollView has already been created so that we can
  104. // insert the disabled view before it to avoid the unnecessary bottom border
  105. // spacing of views::ScrollView when it is not the last child.
  106. DCHECK(scroller());
  107. disabled_view_ = AddChildViewAt(new BluetoothDisabledDetailedView,
  108. GetIndexOf(scroller()).value());
  109. disabled_view_->SetID(
  110. static_cast<int>(BluetoothDetailedViewChildId::kDisabledView));
  111. // Make |disabled_panel_| fill the entire space below the title row so that
  112. // the inner contents can be placed correctly.
  113. box_layout()->SetFlexForView(disabled_view_, 1);
  114. }
  115. void BluetoothDetailedViewImpl::CreatePairNewDeviceView() {
  116. DCHECK(!pair_new_device_view_);
  117. // Check that the views::ScrollView has already been created so that we can
  118. // insert the "pair new device" before it.
  119. DCHECK(scroller());
  120. pair_new_device_view_ =
  121. AddChildViewAt(new views::View(), GetIndexOf(scroller()).value());
  122. pair_new_device_view_->SetLayoutManager(std::make_unique<views::BoxLayout>(
  123. views::BoxLayout::Orientation::kVertical));
  124. pair_new_device_view_->SetID(
  125. static_cast<int>(BluetoothDetailedViewChildId::kPairNewDeviceView));
  126. std::unique_ptr<HoverHighlightView> hover_highlight_view =
  127. std::make_unique<HoverHighlightView>(/*listener=*/this);
  128. hover_highlight_view->SetID(static_cast<int>(
  129. BluetoothDetailedViewChildId::kPairNewDeviceClickableView));
  130. std::unique_ptr<ash::IconButton> button = std::make_unique<ash::IconButton>(
  131. views::Button::PressedCallback(), IconButton::Type::kSmall,
  132. &kSystemMenuBluetoothPlusIcon,
  133. IDS_ASH_STATUS_TRAY_BLUETOOTH_PAIR_NEW_DEVICE);
  134. button->SetCanProcessEventsWithinSubtree(/*can_process=*/false);
  135. button->SetFocusBehavior(
  136. /*focus_behavior=*/views::View::FocusBehavior::NEVER);
  137. hover_highlight_view->AddViewAndLabel(
  138. std::move(button),
  139. l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_PAIR_NEW_DEVICE));
  140. TrayPopupUtils::SetLabelFontList(hover_highlight_view->text_label(),
  141. TrayPopupUtils::FontStyle::kSubHeader);
  142. views::View* separator = pair_new_device_view_->AddChildView(
  143. TrayPopupUtils::CreateListSubHeaderSeparator());
  144. const gfx::Insets padding = separator->GetInsets();
  145. // The hover highlight view does not have top padding by default, unlike the
  146. // following separator. To keep the icon and label centered, and to make the
  147. // entirety of the view "highlightable", remove the padding from the separator
  148. // and add it to both the top and bottom of the hover highlight view.
  149. separator->SetBorder(/*b=*/nullptr);
  150. hover_highlight_view->SetBorder(views::CreateEmptyBorder(
  151. gfx::Insets::TLBR(padding.top(), 0, padding.top(), 0)));
  152. pair_new_device_view_->AddChildViewAt(hover_highlight_view.release(), 0);
  153. }
  154. void BluetoothDetailedViewImpl::CreateTitleRowButtons() {
  155. DCHECK(!settings_button_);
  156. DCHECK(!toggle_button_);
  157. tri_view()->SetContainerVisible(TriView::Container::END, /*visible=*/true);
  158. std::unique_ptr<TrayToggleButton> toggle = std::make_unique<TrayToggleButton>(
  159. base::BindRepeating(&BluetoothDetailedViewImpl::OnToggleClicked,
  160. weak_factory_.GetWeakPtr()),
  161. IDS_ASH_STATUS_TRAY_BLUETOOTH);
  162. toggle->SetID(static_cast<int>(BluetoothDetailedViewChildId::kToggleButton));
  163. toggle_button_ = toggle.get();
  164. tri_view()->AddView(TriView::Container::END, toggle.release());
  165. std::unique_ptr<views::Button> settings =
  166. base::WrapUnique(CreateSettingsButton(
  167. base::BindRepeating(&BluetoothDetailedViewImpl::OnSettingsClicked,
  168. weak_factory_.GetWeakPtr()),
  169. IDS_ASH_STATUS_TRAY_BLUETOOTH_SETTINGS));
  170. settings->SetID(
  171. static_cast<int>(BluetoothDetailedViewChildId::kSettingsButton));
  172. settings_button_ = settings.get();
  173. tri_view()->AddView(TriView::Container::END, settings.release());
  174. }
  175. void BluetoothDetailedViewImpl::OnSettingsClicked() {
  176. if (!TrayPopupUtils::CanOpenWebUISettings())
  177. return;
  178. CloseBubble(); // Deletes |this|.
  179. Shell::Get()->system_tray_model()->client()->ShowBluetoothSettings();
  180. }
  181. void BluetoothDetailedViewImpl::OnToggleClicked() {
  182. const bool toggle_state = toggle_button_->GetIsOn();
  183. delegate()->OnToggleClicked(toggle_state);
  184. // Avoid the situation where there is a delay between the toggle becoming
  185. // enabled/disabled and Bluetooth becoming enabled/disabled by forcing the
  186. // view state to match the toggle state.
  187. UpdateBluetoothEnabledState(toggle_state);
  188. }
  189. } // namespace ash