| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/views/global_media_controls/media_dialog_view.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/observer_list.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/app/vector_icons/vector_icons.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/media/router/media_router_feature.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser_navigator.h" |
| #include "chrome/browser/ui/browser_navigator_params.h" |
| #include "chrome/browser/ui/global_media_controls/media_item_ui_metrics.h" |
| #include "chrome/browser/ui/global_media_controls/media_notification_service.h" |
| #include "chrome/browser/ui/views/chrome_layout_provider.h" |
| #include "chrome/browser/ui/views/controls/rich_hover_button.h" |
| #include "chrome/browser/ui/views/global_media_controls/media_dialog_view_observer.h" |
| #include "chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h" |
| #include "chrome/browser/ui/views/global_media_controls/media_item_ui_footer_view.h" |
| #include "chrome/browser/ui/views/global_media_controls/media_item_ui_helper.h" |
| #include "chrome/browser/ui/views/global_media_controls/media_item_ui_legacy_cast_footer_view.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/global_media_controls/public/media_item_manager.h" |
| #include "components/global_media_controls/public/views/media_item_ui_list_view.h" |
| #include "components/global_media_controls/public/views/media_item_ui_view.h" |
| #include "components/live_caption/caption_util.h" |
| #include "components/live_caption/pref_names.h" |
| #include "components/media_router/browser/media_router.h" |
| #include "components/media_router/browser/media_router_factory.h" |
| #include "components/prefs/pref_change_registrar.h" |
| #include "components/sessions/content/session_tab_helper.h" |
| #include "components/soda/constants.h" |
| #include "components/sync_preferences/pref_service_syncable.h" |
| #include "components/vector_icons/vector_icons.h" |
| #include "content/public/browser/media_session.h" |
| #include "content/public/browser/web_contents.h" |
| #include "media/base/media_switches.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "net/base/url_util.h" |
| #include "services/media_session/public/mojom/media_session.mojom.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/background.h" |
| #include "ui/views/bubble/bubble_frame_view.h" |
| #include "ui/views/controls/button/image_button.h" |
| #include "ui/views/controls/button/toggle_button.h" |
| #include "ui/views/controls/combobox/combobox.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/separator.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/layout/fill_layout.h" |
| #include "ui/views/layout/layout_provider.h" |
| #include "ui/views/style/typography.h" |
| #include "ui/views/views_features.h" |
| |
| using media_session::mojom::MediaSessionAction; |
| |
| namespace { |
| |
| static constexpr int kHorizontalMarginDip = 16; |
| static constexpr int kImageWidthDip = 20; |
| static constexpr int kVerticalMarginDip = 10; |
| |
| std::u16string GetLiveCaptionTitle(PrefService* profile_prefs) { |
| if (!base::FeatureList::IsEnabled(media::kLiveCaptionMultiLanguage)) { |
| return l10n_util::GetStringUTF16( |
| IDS_GLOBAL_MEDIA_CONTROLS_LIVE_CAPTION_ENGLISH_ONLY); |
| } |
| // The selected language is only shown when Live Caption is enabled. |
| if (profile_prefs->GetBoolean(prefs::kLiveCaptionEnabled)) { |
| std::u16string language = speech::GetLanguageDisplayName( |
| prefs::GetLiveCaptionLanguageCode(profile_prefs), |
| g_browser_process->GetApplicationLocale()); |
| return l10n_util::GetStringFUTF16( |
| IDS_GLOBAL_MEDIA_CONTROLS_LIVE_CAPTION_SHOW_LANGUAGE, language); |
| } |
| return l10n_util::GetStringUTF16(IDS_GLOBAL_MEDIA_CONTROLS_LIVE_CAPTION); |
| } |
| |
| void UpdateMediaSessionItemReceiverName( |
| base::WeakPtr<media_message_center::MediaNotificationItem> item, |
| const absl::optional<media_router::MediaRoute>& route) { |
| if (item->SourceType() == |
| media_message_center::SourceType::kLocalMediaSession) { |
| auto* media_session_item = |
| static_cast<global_media_controls::MediaSessionNotificationItem*>( |
| item.get()); |
| if (route.has_value()) { |
| media_session_item->UpdateDeviceName(route->media_sink_name()); |
| } else { |
| media_session_item->UpdateDeviceName(absl::nullopt); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| // static |
| MediaDialogView* MediaDialogView::instance_ = nullptr; |
| |
| // static |
| bool MediaDialogView::has_been_opened_ = false; |
| |
| // static |
| views::Widget* MediaDialogView::ShowDialogFromToolbar( |
| views::View* anchor_view, |
| MediaNotificationService* service, |
| Profile* profile) { |
| return ShowDialog( |
| anchor_view, views::BubbleBorder::TOP_RIGHT, service, profile, nullptr, |
| global_media_controls::GlobalMediaControlsEntryPoint::kToolbarIcon); |
| } |
| |
| // static |
| views::Widget* MediaDialogView::ShowDialogCentered( |
| const gfx::Rect& bounds, |
| MediaNotificationService* service, |
| Profile* profile, |
| content::WebContents* contents, |
| global_media_controls::GlobalMediaControlsEntryPoint entry_point) { |
| auto* widget = ShowDialog(nullptr, views::BubbleBorder::TOP_CENTER, service, |
| profile, contents, entry_point); |
| instance_->SetAnchorRect(bounds); |
| return widget; |
| } |
| |
| // static |
| views::Widget* MediaDialogView::ShowDialog( |
| views::View* anchor_view, |
| views::BubbleBorder::Arrow anchor_position, |
| MediaNotificationService* service, |
| Profile* profile, |
| content::WebContents* contents, |
| global_media_controls::GlobalMediaControlsEntryPoint entry_point) { |
| DCHECK(service); |
| // Hide the previous instance if it exists, since there can only be one dialog |
| // instance at a time. |
| HideDialog(); |
| instance_ = new MediaDialogView(anchor_view, anchor_position, service, |
| profile, contents, entry_point); |
| if (!anchor_view) { |
| instance_->set_has_parent(false); |
| } |
| |
| views::Widget* widget = |
| views::BubbleDialogDelegateView::CreateBubble(instance_); |
| widget->Show(); |
| |
| base::UmaHistogramBoolean("Media.GlobalMediaControls.RepeatUsage", |
| has_been_opened_); |
| base::UmaHistogramEnumeration("Media.GlobalMediaControls.EntryPoint", |
| entry_point); |
| has_been_opened_ = true; |
| |
| return widget; |
| } |
| |
| // static |
| void MediaDialogView::HideDialog() { |
| if (IsShowing()) { |
| instance_->service_->media_item_manager()->SetDialogDelegate(nullptr); |
| speech::SodaInstaller::GetInstance()->RemoveObserver(instance_); |
| instance_->GetWidget()->Close(); |
| } |
| |
| // Set |instance_| to nullptr so that |IsShowing()| returns false immediately. |
| // We also set to nullptr in |WindowClosing()| (which happens asynchronously), |
| // since |HideDialog()| is not always called. |
| instance_ = nullptr; |
| } |
| |
| // static |
| bool MediaDialogView::IsShowing() { |
| return instance_ != nullptr; |
| } |
| |
| global_media_controls::MediaItemUI* MediaDialogView::ShowMediaItem( |
| const std::string& id, |
| base::WeakPtr<media_message_center::MediaNotificationItem> item) { |
| auto view = BuildMediaItemUIView(id, item); |
| auto* view_ptr = view.get(); |
| view_ptr->AddObserver(this); |
| observed_items_[id] = view_ptr; |
| |
| active_sessions_view_->ShowItem(id, std::move(view)); |
| UpdateBubbleSize(); |
| |
| for (auto& observer : observers_) { |
| observer.OnMediaSessionShown(); |
| } |
| return view_ptr; |
| } |
| |
| void MediaDialogView::HideMediaItem(const std::string& id) { |
| active_sessions_view_->HideItem(id); |
| |
| if (active_sessions_view_->empty()) { |
| HideDialog(); |
| } else { |
| UpdateBubbleSize(); |
| } |
| |
| for (auto& observer : observers_) { |
| observer.OnMediaSessionHidden(); |
| } |
| } |
| |
| void MediaDialogView::RefreshMediaItem( |
| const std::string& id, |
| base::WeakPtr<media_message_center::MediaNotificationItem> item) { |
| DCHECK(observed_items_[id]); |
| |
| auto device_selector_view = |
| BuildDeviceSelector(id, item, service_, service_, profile_, entry_point_); |
| observed_items_[id]->UpdateFooterView( |
| BuildFooterView(id, item, device_selector_view.get())); |
| observed_items_[id]->UpdateDeviceSelector(std::move(device_selector_view)); |
| |
| UpdateBubbleSize(); |
| } |
| |
| void MediaDialogView::HideMediaDialog() { |
| HideDialog(); |
| } |
| |
| void MediaDialogView::Focus() { |
| RequestFocus(); |
| } |
| |
| void MediaDialogView::AddedToWidget() { |
| int corner_radius = views::LayoutProvider::Get()->GetCornerRadiusMetric( |
| views::Emphasis::kHigh); |
| views::BubbleFrameView* frame = GetBubbleFrameView(); |
| if (frame) { |
| frame->SetCornerRadius(corner_radius); |
| } |
| if (entry_point_ == |
| global_media_controls::GlobalMediaControlsEntryPoint::kPresentation) { |
| service_->SetDialogDelegateForWebContents( |
| this, web_contents_for_presentation_request_); |
| } else { |
| service_->media_item_manager()->SetDialogDelegate(this); |
| } |
| speech::SodaInstaller::GetInstance()->AddObserver(this); |
| } |
| |
| gfx::Size MediaDialogView::CalculatePreferredSize() const { |
| // If we have active sessions, then fit to them. |
| if (!active_sessions_view_->empty()) { |
| return views::BubbleDialogDelegateView::CalculatePreferredSize(); |
| } |
| // Otherwise, use a standard size for bubble dialogs. |
| const int width = ChromeLayoutProvider::Get()->GetDistanceMetric( |
| views::DISTANCE_BUBBLE_PREFERRED_WIDTH); |
| return gfx::Size(width, 1); |
| } |
| |
| void MediaDialogView::UpdateBubbleSize() { |
| SizeToContents(); |
| if (!captions::IsLiveCaptionFeatureSupported()) { |
| return; |
| } |
| const int width = active_sessions_view_->GetPreferredSize().width(); |
| const int live_caption_height = |
| live_caption_container_->GetPreferredSize().height(); |
| live_caption_container_->SetPreferredSize( |
| gfx::Size(width, live_caption_height)); |
| |
| if (base::FeatureList::IsEnabled(media::kLiveTranslate)) { |
| const int live_translate_height = |
| live_translate_container_->GetPreferredSize().height(); |
| live_translate_container_->SetPreferredSize( |
| gfx::Size(width, live_translate_height)); |
| |
| live_translate_subtitle_->SetTextStyle(views::style::STYLE_SECONDARY); |
| |
| live_translate_label_wrapper_->SetPreferredSize(gfx::Size( |
| width, live_translate_label_wrapper_->GetPreferredSize().height())); |
| |
| // Align the combo box with the text labels. |
| const int target_language_container_height = |
| target_language_container_->GetPreferredSize().height(); |
| target_language_container_->SetPreferredSize( |
| gfx::Size(width, target_language_container_height)); |
| target_language_combobox_->SetPreferredSize( |
| gfx::Size(width - 2 * (kImageWidthDip + kHorizontalMarginDip + |
| ChromeLayoutProvider::Get()->GetDistanceMetric( |
| views::DISTANCE_RELATED_LABEL_HORIZONTAL)), |
| target_language_container_height)); |
| |
| separator_->SetPreferredLength(width); |
| caption_settings_button_->SetPreferredSize( |
| gfx::Size(width, live_caption_height)); |
| } |
| } |
| |
| void MediaDialogView::OnLiveCaptionEnabledChanged() { |
| bool enabled = profile_->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled); |
| |
| // Do not update the title if SODA is currently downloading. |
| if (!speech::SodaInstaller::GetInstance()->IsSodaDownloading( |
| speech::GetLanguageCode( |
| prefs::GetLiveCaptionLanguageCode(profile_->GetPrefs())))) { |
| SetLiveCaptionTitle(GetLiveCaptionTitle(profile_->GetPrefs())); |
| } |
| |
| live_caption_button_->SetIsOn(enabled); |
| |
| if (base::FeatureList::IsEnabled(media::kLiveTranslate)) { |
| live_translate_container_->SetVisible(enabled); |
| } |
| |
| UpdateBubbleSize(); |
| } |
| |
| void MediaDialogView::OnLiveTranslateEnabledChanged() { |
| bool enabled = profile_->GetPrefs()->GetBoolean(prefs::kLiveTranslateEnabled); |
| live_translate_button_->SetIsOn(enabled); |
| target_language_container_->SetVisible(enabled); |
| UpdateBubbleSize(); |
| } |
| |
| void MediaDialogView::OnMediaItemUISizeChanged() { |
| UpdateBubbleSize(); |
| } |
| |
| void MediaDialogView::OnMediaItemUIMetadataChanged() { |
| for (auto& observer : observers_) { |
| observer.OnMediaSessionMetadataUpdated(); |
| } |
| } |
| |
| void MediaDialogView::OnMediaItemUIActionsChanged() { |
| for (auto& observer : observers_) { |
| observer.OnMediaSessionActionsChanged(); |
| } |
| } |
| |
| void MediaDialogView::OnMediaItemUIDestroyed(const std::string& id) { |
| auto iter = observed_items_.find(id); |
| DCHECK(iter != observed_items_.end()); |
| |
| iter->second->RemoveObserver(this); |
| observed_items_.erase(iter); |
| } |
| |
| void MediaDialogView::AddObserver(MediaDialogViewObserver* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void MediaDialogView::RemoveObserver(MediaDialogViewObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void MediaDialogView::TargetLanguageChanged() { |
| static_cast<LiveTranslateComboboxModel*>( |
| target_language_combobox_->GetModel()) |
| ->UpdateTargetLanguageIndex( |
| target_language_combobox_->GetSelectedIndex().value()); |
| } |
| |
| const std::map<const std::string, global_media_controls::MediaItemUIView*>& |
| MediaDialogView::GetItemsForTesting() const { |
| return active_sessions_view_->items_for_testing(); // IN-TEST |
| } |
| |
| const global_media_controls::MediaItemUIListView* |
| MediaDialogView::GetListViewForTesting() const { |
| return active_sessions_view_; |
| } |
| |
| MediaDialogView::MediaDialogView( |
| views::View* anchor_view, |
| views::BubbleBorder::Arrow anchor_position, |
| MediaNotificationService* service, |
| Profile* profile, |
| content::WebContents* contents, |
| global_media_controls::GlobalMediaControlsEntryPoint entry_point) |
| : BubbleDialogDelegateView(anchor_view, anchor_position), |
| service_(service), |
| profile_(profile->GetOriginalProfile()), |
| active_sessions_view_(AddChildView( |
| std::make_unique<global_media_controls::MediaItemUIListView>())), |
| web_contents_for_presentation_request_(contents), |
| entry_point_(entry_point) { |
| // Enable layer based clipping to ensure children using layers are clipped |
| // appropriately. |
| SetPaintClientToLayer(true); |
| SetButtons(ui::DIALOG_BUTTON_NONE); |
| SetAccessibleTitle( |
| l10n_util::GetStringUTF16(IDS_GLOBAL_MEDIA_CONTROLS_DIALOG_NAME)); |
| DCHECK(service_); |
| |
| pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>(); |
| pref_change_registrar_->Init(profile->GetPrefs()); |
| pref_change_registrar_->Add( |
| prefs::kLiveCaptionEnabled, |
| base::BindRepeating(&MediaDialogView::OnLiveCaptionEnabledChanged, |
| base::Unretained(this))); |
| pref_change_registrar_->Add( |
| prefs::kLiveTranslateEnabled, |
| base::BindRepeating(&MediaDialogView::OnLiveTranslateEnabledChanged, |
| base::Unretained(this))); |
| } |
| |
| MediaDialogView::~MediaDialogView() { |
| for (auto item_pair : observed_items_) { |
| item_pair.second->RemoveObserver(this); |
| } |
| } |
| |
| void MediaDialogView::Init() { |
| // Remove margins. |
| set_margins(gfx::Insets()); |
| if (!captions::IsLiveCaptionFeatureSupported()) { |
| SetLayoutManager(std::make_unique<views::FillLayout>()); |
| return; |
| } |
| SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kVertical)) |
| ->set_cross_axis_alignment(views::BoxLayout::CrossAxisAlignment::kStart); |
| |
| InitializeLiveCaptionSection(); |
| if (base::FeatureList::IsEnabled(media::kLiveTranslate)) { |
| InitializeLiveTranslateSection(); |
| |
| separator_ = AddChildView(std::make_unique<views::Separator>()); |
| separator_->SetOrientation(views::Separator::Orientation::kHorizontal); |
| InitializeCaptionSettingsSection(); |
| } |
| } |
| |
| void MediaDialogView::WindowClosing() { |
| if (instance_ == this) { |
| instance_ = nullptr; |
| service_->media_item_manager()->SetDialogDelegate(nullptr); |
| speech::SodaInstaller::GetInstance()->RemoveObserver(this); |
| } |
| } |
| |
| void MediaDialogView::OnLiveCaptionButtonPressed() { |
| bool enabled = !profile_->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled); |
| profile_->GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled, enabled); |
| base::UmaHistogramBoolean( |
| "Accessibility.LiveCaption.EnableFromGlobalMediaControls", enabled); |
| } |
| |
| void MediaDialogView::OnLiveTranslateButtonPressed() { |
| bool enabled = |
| !profile_->GetPrefs()->GetBoolean(prefs::kLiveTranslateEnabled); |
| profile_->GetPrefs()->SetBoolean(prefs::kLiveTranslateEnabled, enabled); |
| } |
| |
| void MediaDialogView::OnSettingsButtonPressed() { |
| NavigateParams navigate_params(profile_, |
| GURL(captions::GetCaptionSettingsUrl()), |
| ui::PAGE_TRANSITION_LINK); |
| navigate_params.window_action = NavigateParams::WindowAction::SHOW_WINDOW; |
| navigate_params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; |
| Navigate(&navigate_params); |
| } |
| |
| void MediaDialogView::OnSodaInstalled(speech::LanguageCode language_code) { |
| if (!prefs::IsLanguageCodeForLiveCaption(language_code, |
| profile_->GetPrefs())) { |
| return; |
| } |
| speech::SodaInstaller::GetInstance()->RemoveObserver(this); |
| SetLiveCaptionTitle(GetLiveCaptionTitle(profile_->GetPrefs())); |
| } |
| |
| void MediaDialogView::OnSodaInstallError( |
| speech::LanguageCode language_code, |
| speech::SodaInstaller::ErrorCode error_code) { |
| // Check that language code matches the selected language for Live Caption or |
| // is LanguageCode::kNone (signifying the SODA binary failed). |
| if (!prefs::IsLanguageCodeForLiveCaption(language_code, |
| profile_->GetPrefs()) && |
| language_code != speech::LanguageCode::kNone) { |
| return; |
| } |
| |
| std::u16string error_message; |
| switch (error_code) { |
| case speech::SodaInstaller::ErrorCode::kUnspecifiedError: { |
| error_message = l10n_util::GetStringUTF16( |
| IDS_GLOBAL_MEDIA_CONTROLS_LIVE_CAPTION_DOWNLOAD_ERROR); |
| break; |
| } |
| case speech::SodaInstaller::ErrorCode::kNeedsReboot: { |
| error_message = l10n_util::GetStringUTF16( |
| IDS_GLOBAL_MEDIA_CONTROLS_LIVE_CAPTION_DOWNLOAD_ERROR_REBOOT_REQUIRED); |
| break; |
| } |
| } |
| |
| SetLiveCaptionTitle(error_message); |
| } |
| |
| void MediaDialogView::OnSodaProgress(speech::LanguageCode language_code, |
| int progress) { |
| // Check that language code matches the selected language for Live Caption or |
| // is LanguageCode::kNone (signifying the SODA binary has progress). |
| if (!prefs::IsLanguageCodeForLiveCaption(language_code, |
| profile_->GetPrefs()) && |
| language_code != speech::LanguageCode::kNone) { |
| return; |
| } |
| SetLiveCaptionTitle(l10n_util::GetStringFUTF16Int( |
| IDS_GLOBAL_MEDIA_CONTROLS_LIVE_CAPTION_DOWNLOAD_PROGRESS, progress)); |
| } |
| |
| void MediaDialogView::InitializeLiveCaptionSection() { |
| auto live_caption_container = std::make_unique<View>(); |
| |
| auto live_caption_image = std::make_unique<views::ImageView>(); |
| live_caption_image->SetImage(ui::ImageModel::FromVectorIcon( |
| vector_icons::kLiveCaptionOnIcon, ui::kColorIcon, kImageWidthDip)); |
| live_caption_container->AddChildView(std::move(live_caption_image)); |
| |
| auto live_caption_title = |
| std::make_unique<views::Label>(GetLiveCaptionTitle(profile_->GetPrefs())); |
| live_caption_title->SetHorizontalAlignment( |
| gfx::HorizontalAlignment::ALIGN_LEFT); |
| live_caption_title->SetMultiLine(true); |
| live_caption_title_ = |
| live_caption_container->AddChildView(std::move(live_caption_title)); |
| |
| auto live_caption_button = std::make_unique<views::ToggleButton>( |
| base::BindRepeating(&MediaDialogView::OnLiveCaptionButtonPressed, |
| base::Unretained(this))); |
| live_caption_button->SetIsOn( |
| profile_->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled)); |
| live_caption_button->SetAccessibleName(live_caption_title_->GetText()); |
| live_caption_button_ = |
| live_caption_container->AddChildView(std::move(live_caption_button)); |
| |
| auto* live_caption_container_layout = |
| live_caption_container->SetLayoutManager( |
| std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, |
| gfx::Insets::TLBR( |
| kVerticalMarginDip, kHorizontalMarginDip, kVerticalMarginDip, |
| kHorizontalMarginDip - |
| live_caption_button_->GetVisualHorizontalMargin()), |
| ChromeLayoutProvider::Get()->GetDistanceMetric( |
| views::DISTANCE_RELATED_LABEL_HORIZONTAL))); |
| live_caption_container_layout->SetFlexForView(live_caption_title_, 1); |
| live_caption_container_ = AddChildView(std::move(live_caption_container)); |
| } |
| |
| void MediaDialogView::InitializeLiveTranslateSection() { |
| auto live_translate_container = std::make_unique<View>(); |
| live_translate_container->SetVisible( |
| profile_->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled)); |
| |
| auto live_translate_image = std::make_unique<views::ImageView>(); |
| live_translate_image->SetImage(ui::ImageModel::FromVectorIcon( |
| kTranslateChromeRefreshIcon, ui::kColorIcon, kImageWidthDip)); |
| live_translate_container->AddChildView(std::move(live_translate_image)); |
| |
| auto live_translate_label_wrapper = std::make_unique<View>(); |
| live_translate_label_wrapper->SetLayoutManager( |
| std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kVertical)); |
| auto live_translate_title = |
| std::make_unique<views::Label>(l10n_util::GetStringUTF16( |
| IDS_SETTINGS_CAPTIONS_ENABLE_LIVE_TRANSLATE_TITLE)); |
| live_translate_title->SetHorizontalAlignment( |
| gfx::HorizontalAlignment::ALIGN_LEFT); |
| live_translate_title->SetMultiLine(true); |
| live_translate_title_ = live_translate_label_wrapper->AddChildView( |
| std::move(live_translate_title)); |
| |
| auto live_translate_subtitle = |
| std::make_unique<views::Label>(l10n_util::GetStringUTF16( |
| IDS_GLOBAL_MEDIA_CONTROLS_LIVE_TRANSLATE_SUBTITLE)); |
| live_translate_subtitle->SetHorizontalAlignment( |
| gfx::HorizontalAlignment::ALIGN_LEFT); |
| live_translate_subtitle_ = live_translate_label_wrapper->AddChildView( |
| std::move(live_translate_subtitle)); |
| |
| live_translate_label_wrapper_ = live_translate_container->AddChildView( |
| std::move(live_translate_label_wrapper)); |
| |
| auto live_translate_button = std::make_unique<views::ToggleButton>( |
| base::BindRepeating(&MediaDialogView::OnLiveTranslateButtonPressed, |
| base::Unretained(this))); |
| live_translate_button->SetIsOn( |
| profile_->GetPrefs()->GetBoolean(prefs::kLiveTranslateEnabled)); |
| live_translate_button->SetAccessibleName(live_translate_title_->GetText()); |
| auto* live_translate_container_layout = |
| live_translate_container->SetLayoutManager( |
| std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, |
| gfx::Insets::TLBR( |
| kVerticalMarginDip, kHorizontalMarginDip, kVerticalMarginDip, |
| kHorizontalMarginDip - |
| live_translate_button->GetVisualHorizontalMargin()), |
| ChromeLayoutProvider::Get()->GetDistanceMetric( |
| views::DISTANCE_RELATED_LABEL_HORIZONTAL))); |
| live_translate_container_layout->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kStart); |
| live_translate_container_layout->SetFlexForView(live_translate_label_wrapper_, |
| 1); |
| live_translate_button_ = |
| live_translate_container->AddChildView(std::move(live_translate_button)); |
| live_translate_container_ = AddChildView(std::move(live_translate_container)); |
| |
| // Initialize the target language container. |
| auto target_language_container = std::make_unique<View>(); |
| target_language_container->SetBorder( |
| views::CreateEmptyBorder(gfx::Insets::TLBR(0, 0, kVerticalMarginDip, 0))); |
| target_language_container->SetVisible( |
| profile_->GetPrefs()->GetBoolean(prefs::kLiveTranslateEnabled)); |
| target_language_container |
| ->SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kVertical)) |
| ->set_cross_axis_alignment(views::BoxLayout::CrossAxisAlignment::kCenter); |
| |
| auto target_language_model = |
| std::make_unique<LiveTranslateComboboxModel>(profile_); |
| auto target_language_combobox = |
| std::make_unique<views::Combobox>(std::move(target_language_model)); |
| target_language_combobox->SetCallback(base::BindRepeating( |
| &MediaDialogView::TargetLanguageChanged, base::Unretained(this))); |
| target_language_combobox->SetAccessibleName(l10n_util::GetStringUTF16( |
| IDS_GLOBAL_MEDIA_CONTROLS_LIVE_TRANSLATE_TARGET_LANGUAGE_ACCNAME)); |
| target_language_combobox_ = target_language_container->AddChildView( |
| std::move(target_language_combobox)); |
| target_language_container_ = |
| AddChildView(std::move(target_language_container)); |
| } |
| |
| void MediaDialogView::InitializeCaptionSettingsSection() { |
| auto caption_settings_container = std::make_unique<View>(); |
| caption_settings_container->SetLayoutManager( |
| std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, |
| gfx::Insets::VH(kVerticalMarginDip, 0))); |
| auto caption_settings_button = std::make_unique<RichHoverButton>( |
| base::BindRepeating(&MediaDialogView::OnSettingsButtonPressed, |
| base::Unretained(this)), |
| ui::ImageModel::FromVectorIcon(vector_icons::kSettingsIcon, |
| ui::kColorIcon, kImageWidthDip), |
| l10n_util::GetStringUTF16(IDS_GLOBAL_MEDIA_CONTROLS_CAPTION_SETTINGS), |
| std::u16string(), std::u16string(), std::u16string(), |
| ui::ImageModel::FromVectorIcon(vector_icons::kLaunchIcon, ui::kColorIcon, |
| kImageWidthDip)); |
| caption_settings_button_ = caption_settings_container->AddChildView( |
| std::move(caption_settings_button)); |
| caption_settings_container_ = |
| AddChildView(std::move(caption_settings_container)); |
| } |
| |
| void MediaDialogView::SetLiveCaptionTitle(const std::u16string& new_text) { |
| live_caption_title_->SetText(new_text); |
| UpdateBubbleSize(); |
| } |
| |
| std::unique_ptr<global_media_controls::MediaItemUIFooter> |
| MediaDialogView::BuildFooterView( |
| const std::string& id, |
| base::WeakPtr<media_message_center::MediaNotificationItem> item, |
| MediaItemUIDeviceSelectorView* device_selector_view) { |
| // Show a footer view when media::kGlobalMediaControlsModernUI is enabled. |
| std::unique_ptr<global_media_controls::MediaItemUIFooter> footer_view; |
| if (base::FeatureList::IsEnabled(media::kGlobalMediaControlsModernUI)) { |
| footer_view = std::make_unique<MediaItemUIFooterView>(base::NullCallback()); |
| if (device_selector_view) { |
| auto* modern_footer = |
| static_cast<MediaItemUIFooterView*>(footer_view.get()); |
| modern_footer->SetDelegate(device_selector_view); |
| device_selector_view->AddObserver(modern_footer); |
| } |
| return footer_view; |
| } |
| |
| // Show a footer view for a Cast item. |
| if (item->SourceType() == media_message_center::SourceType::kCast && |
| media_router::GlobalMediaControlsCastStartStopEnabled(profile_)) { |
| return std::make_unique<MediaItemUILegacyCastFooterView>( |
| base::BindRepeating( |
| &CastMediaNotificationItem::StopCasting, |
| static_cast<CastMediaNotificationItem*>(item.get())->GetWeakPtr(), |
| entry_point_)); |
| } |
| |
| // Show a footer view for a local media item when it has an associated Remote |
| // Playback session or a Tab Mirroring Session. |
| if (item->SourceType() != |
| media_message_center::SourceType::kLocalMediaSession) { |
| return nullptr; |
| } |
| |
| auto route = GetSessionRoute(id, item, profile_); |
| UpdateMediaSessionItemReceiverName(item, route); |
| if (!route.has_value()) { |
| return nullptr; |
| } |
| const auto& route_id = route->media_route_id(); |
| auto cast_mode = HasRemotePlaybackRoute(item) |
| ? media_router::MediaCastMode::REMOTE_PLAYBACK |
| : media_router::MediaCastMode::TAB_MIRROR; |
| auto stop_casting_cb = base::BindRepeating( |
| [](const std::string& route_id, media_router::MediaRouter* router, |
| global_media_controls::GlobalMediaControlsEntryPoint entry_point, |
| media_router::MediaCastMode cast_mode) { |
| router->TerminateRoute(route_id); |
| MediaItemUIMetrics::RecordStopCastingMetrics(cast_mode, entry_point); |
| if (cast_mode == media_router::MediaCastMode::TAB_MIRROR) { |
| MediaDialogView::HideDialog(); |
| } |
| }, |
| route_id, |
| media_router::MediaRouterFactory::GetApiForBrowserContext(profile_), |
| entry_point_, cast_mode); |
| return std::make_unique<MediaItemUILegacyCastFooterView>( |
| std::move(stop_casting_cb)); |
| } |
| |
| std::unique_ptr<global_media_controls::MediaItemUIView> |
| MediaDialogView::BuildMediaItemUIView( |
| const std::string& id, |
| base::WeakPtr<media_message_center::MediaNotificationItem> item) { |
| auto device_selector_view = |
| BuildDeviceSelector(id, item, service_, service_, profile_, entry_point_); |
| auto footer_view = BuildFooterView(id, item, device_selector_view.get()); |
| |
| return std::make_unique<global_media_controls::MediaItemUIView>( |
| id, item, std::move(footer_view), std::move(device_selector_view)); |
| } |
| |
| BEGIN_METADATA(MediaDialogView, views::BubbleDialogDelegateView) |
| END_METADATA |