[go: nahoru, domu]

blob: b0b4ef740f84eeebc88004b2f15605690fb980dd [file] [log] [blame]
// Copyright 2020 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/ash/sharesheet/sharesheet_bubble_view.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include "ash/public/cpp/ash_typography.h"
#include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
#include "ash/style/ash_color_provider.h"
#include "ash/style/typography.h"
#include "base/check_op.h"
#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/about_flags.h"
#include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
#include "chrome/browser/nearby_sharing/common/nearby_share_resource_getter.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sharesheet/sharesheet_metrics.h"
#include "chrome/browser/sharesheet/sharesheet_service_delegator.h"
#include "chrome/browser/ui/ash/sharesheet/sharesheet_constants.h"
#include "chrome/browser/ui/ash/sharesheet/sharesheet_expand_button.h"
#include "chrome/browser/ui/ash/sharesheet/sharesheet_header_view.h"
#include "chrome/browser/ui/ash/sharesheet/sharesheet_target_button.h"
#include "chrome/browser/ui/ash/sharesheet/sharesheet_util.h"
#include "chrome/common/chrome_features.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/components/sharesheet/constants.h"
#include "chromeos/constants/chromeos_features.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "ui/accessibility/ax_enums.mojom-forward.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/compositor/closure_animation_observer.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/display/screen.h"
#include "ui/display/tablet_state.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/image_button_factory.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/separator.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/layout/table_layout_view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
namespace {
// TODO(crbug.com/1097623) Many of below values are sums of each other and
// can be removed.
// Sizes are in px.
constexpr int kButtonWidth = 92;
constexpr int kCornerRadius = 12;
constexpr int kBubbleTopPaddingFromWindow = 28;
constexpr int kMaxTargetsPerRow = 4;
constexpr int kMaxRowsForDefaultView = 2;
// TargetViewHeight is 2*kButtonHeight + kButtonPadding
constexpr int kTargetViewHeight = 216;
// TargetViewExpandedHeight is default_view_->GetPreferredSize().height() + apps
// list text + 2*kExpandedViewPaddingTop + expanded_view_->FirstRow().height().
// TODO(crbug.com/1097623): Update this to a layout that will allow us to get
// the height of the first row.
constexpr int kTargetViewExpandedHeight = 382;
constexpr int kExpandViewPaddingTop = 16;
constexpr int kExpandViewPaddingBottom = 8;
constexpr int kShortSpacing = 10;
constexpr auto kAnimateDelay = base::Milliseconds(100);
constexpr auto kQuickAnimateTime = base::Milliseconds(100);
constexpr auto kSlowAnimateTime = base::Milliseconds(200);
void SetUpTargetColumns(views::TableLayoutView* view) {
for (int i = 0; i < kMaxTargetsPerRow; i++) {
view->AddColumn(views::LayoutAlignment::kCenter,
views::LayoutAlignment::kStart, 0,
views::TableLayout::ColumnSize::kFixed, kButtonWidth, 0);
}
}
bool IsKeyboardCodeArrow(ui::KeyboardCode key_code) {
return key_code == ui::VKEY_UP || key_code == ui::VKEY_DOWN ||
key_code == ui::VKEY_RIGHT || key_code == ui::VKEY_LEFT;
}
void RecordMimeTypeMetric(const apps::IntentPtr& intent) {
auto mime_types_to_record =
::sharesheet::SharesheetMetrics::GetMimeTypesFromIntentForMetrics(intent);
for (auto& mime_type : mime_types_to_record) {
::sharesheet::SharesheetMetrics::RecordSharesheetMimeType(mime_type);
}
}
} // namespace
namespace ash {
namespace sharesheet {
class SharesheetBubbleView::SharesheetParentWidgetObserver
: public views::WidgetObserver {
public:
SharesheetParentWidgetObserver(SharesheetBubbleView* owner,
views::Widget* widget)
: owner_(owner) {
observer_.Observe(widget);
}
~SharesheetParentWidgetObserver() override = default;
// WidgetObserver:
void OnWidgetDestroying(views::Widget* widget) override {
DCHECK(observer_.IsObservingSource(widget));
observer_.Reset();
// |this| may be destroyed here!
// TODO(crbug.com/1188938) Code clean up.
// There should be something here telling SharesheetBubbleView
// that its parent widget is closing and therefore it should
// also close. Or we should try to inherit the widget changes from
// BubbleDialogDelegate and not have this class here at all.
}
void OnWidgetBoundsChanged(views::Widget* widget,
const gfx::Rect& bounds) override {
owner_->UpdateAnchorPosition();
}
private:
raw_ptr<SharesheetBubbleView> owner_;
base::ScopedObservation<views::Widget, views::WidgetObserver> observer_{this};
};
SharesheetBubbleView::SharesheetBubbleView(
gfx::NativeWindow native_window,
::sharesheet::SharesheetServiceDelegator* delegator)
: delegator_(delegator) {
CHECK(native_window);
CHECK(delegator_);
SetID(SHARESHEET_BUBBLE_VIEW_ID);
// We set the dialog role because views::BubbleDialogDelegate defaults this to
// an alert dialog. This would make screen readers announce all of this dialog
// which is undesirable.
SetAccessibleWindowRole(ax::mojom::Role::kDialog);
SetAccessibleTitle(l10n_util::GetStringUTF16(IDS_SHARESHEET_TITLE_LABEL));
AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
set_parent_window(native_window);
views::Widget* const widget =
views::Widget::GetWidgetForNativeWindow(native_window);
CHECK(widget);
parent_view_ = widget->GetRootView();
parent_widget_observer_ =
std::make_unique<SharesheetParentWidgetObserver>(this, widget);
InitBubble();
}
SharesheetBubbleView::~SharesheetBubbleView() {
// TODO(https://crbug.com/1249491): While this is harmless, it should not be
// necessary unless something fishy is happening with the behavior of layer
// animations around widget teardown.
if (close_callback_) {
std::move(close_callback_).Run(views::Widget::ClosedReason::kUnspecified);
}
display::Screen::GetScreen()->RemoveObserver(this);
}
void SharesheetBubbleView::ShowBubble(
std::vector<TargetInfo> targets,
apps::IntentPtr intent,
::sharesheet::DeliveredCallback delivered_callback,
::sharesheet::CloseCallback close_callback) {
intent_ = std::move(intent);
delivered_callback_ = std::move(delivered_callback);
close_callback_ = std::move(close_callback);
main_view_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
header_view_ =
main_view_->AddChildView(std::make_unique<SharesheetHeaderView>(
intent_->Clone(), delegator_->GetProfile()));
body_view_ = main_view_->AddChildView(std::make_unique<views::View>());
body_view_->SetID(BODY_VIEW_ID);
body_view_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
footer_view_ = main_view_->AddChildView(std::make_unique<views::View>());
footer_view_->SetID(FOOTER_VIEW_ID);
auto* footer_layout =
footer_view_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal,
gfx::Insets::VH(kFooterDefaultVerticalPadding, 0)));
footer_layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kCenter);
footer_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
// There is always at least 1 target as Copy To Clipboard is always visible.
CHECK_GT(targets.size(), 0u);
header_body_separator_ =
body_view_->AddChildView(std::make_unique<views::Separator>());
if (chromeos::features::IsJellyEnabled()) {
header_body_separator_->SetColorId(cros_tokens::kCrosSysSeparator);
}
const size_t targets_size = targets.size();
auto scroll_view = std::make_unique<views::ScrollView>();
scroll_view->SetContents(MakeScrollableTargetView(std::move(targets)));
scroll_view->ClipHeightTo(kTargetViewHeight, kTargetViewExpandedHeight);
body_view_->AddChildView(std::move(scroll_view));
if (expanded_view_) {
body_footer_separator_ =
body_view_->AddChildView(std::make_unique<views::Separator>());
if (chromeos::features::IsJellyEnabled()) {
body_footer_separator_->SetColorId(cros_tokens::kCrosSysSeparator);
}
expand_button_ =
footer_view_->AddChildView(std::make_unique<SharesheetExpandButton>(
base::BindRepeating(&SharesheetBubbleView::ExpandButtonPressed,
base::Unretained(this))));
} else if (targets_size <= kMaxTargetsPerRow * kMaxRowsForDefaultView) {
// When we have between 1 and 8 targets inclusive. Update |footer_layout|
// padding.
footer_layout->set_inside_border_insets(
gfx::Insets::VH(kFooterNoExtensionVerticalPadding, 0));
}
SetUpAndShowBubble();
}
void SharesheetBubbleView::ShowNearbyShareBubbleForArc(
apps::IntentPtr intent,
::sharesheet::DeliveredCallback delivered_callback,
::sharesheet::CloseCallback close_callback) {
// Disable close when clicking outside bubble for Nearby Share.
close_on_deactivate_ = false;
close_callback_ = std::move(close_callback);
intent_ = std::move(intent);
// Set up the bubble so that the nearby share dialog can be triggered within
// the sharesheet.
SetUpAndShowBubble();
if (delivered_callback) {
std::move(delivered_callback).Run(::sharesheet::SharesheetResult::kSuccess);
}
// When the Nearby Share target is shown, it will transform from the original
// sharesheet bubble to the nearby share dialog. This animation requires an
// original rectangle to transform from, so the size of the bubble cannot be
// 0. In this instance, we have not populated the sharesheet with anything, as
// it'll never be shown, so the dynamic sizing will set the height to 0. To
// get around that, we set the height to 1, so there is a starting rectangle
// to transform from.
//
// Having a height of "1" means that the animation for showing Nearby Share
// from ARC++ is mostly a vertical expansion, instead of how it looks in a
// normal sharesheet where there's a slight vertical and slight horizontal
// change. We could try calculate the correct "empty" size of the sharesheet
// and use that instead for a more consistent UI experience.
height_ = 1;
const std::u16string target_name =
features::IsNameEnabled()
? NearbyShareResourceGetter::GetInstance()->GetStringWithFeatureName(
IDS_NEARBY_SHARE_FEATURE_NAME_PH)
: l10n_util::GetStringUTF16(IDS_NEARBY_SHARE_FEATURE_NAME);
delegator_->OnTargetSelected(target_name, ::sharesheet::TargetType::kAction,
std::move(intent_), share_action_view_);
}
std::unique_ptr<views::View> SharesheetBubbleView::MakeScrollableTargetView(
std::vector<TargetInfo> targets) {
// Set up default and expanded views.
auto default_view = std::make_unique<views::TableLayoutView>();
default_view->SetProperty(views::kMarginsKey, gfx::Insets::VH(0, kSpacing));
SetUpTargetColumns(default_view.get());
default_view->AddPaddingRow(views::TableLayout::kFixedSize, kShortSpacing);
std::unique_ptr<views::BoxLayoutView> expanded_view_container;
views::TableLayoutView* expanded_view_table = nullptr;
if (targets.size() > kMaxTargetsPerRow * kMaxRowsForDefaultView) {
expanded_view_container = std::make_unique<views::BoxLayoutView>();
expanded_view_container->SetProperty(views::kMarginsKey,
gfx::Insets::VH(0, kSpacing));
expanded_view_container->SetOrientation(
views::BoxLayout::Orientation::kVertical);
expanded_view_container
->AddChildView(
chromeos::features::IsJellyEnabled()
? CreateShareLabel(
l10n_util::GetStringUTF16(IDS_SHARESHEET_APPS_LIST_LABEL),
TypographyToken::kCrosHeadline1,
cros_tokens::kCrosSysOnSurface, gfx::ALIGN_CENTER)
: CreateShareLabel(
l10n_util::GetStringUTF16(IDS_SHARESHEET_APPS_LIST_LABEL),
CONTEXT_SHARESHEET_BUBBLE_BODY, kSubtitleTextLineHeight,
AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::
kTextColorPrimary),
gfx::ALIGN_CENTER))
->SetProperty(views::kMarginsKey,
gfx::Insets::TLBR(kExpandViewPaddingTop, 0,
kExpandViewPaddingBottom, 0));
expanded_view_table = expanded_view_container->AddChildView(
std::make_unique<views::TableLayoutView>());
SetUpTargetColumns(expanded_view_table);
}
PopulateLayoutsWithTargets(std::move(targets), default_view.get(),
expanded_view_table);
default_view->AddPaddingRow(views::TableLayout::kFixedSize, kShortSpacing);
auto scrollable_view = std::make_unique<views::View>();
auto* layout =
scrollable_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kCenter);
default_view_ = scrollable_view->AddChildView(std::move(default_view));
default_view_->SetID(TARGETS_DEFAULT_VIEW_ID);
if (expanded_view_container) {
expanded_view_separator_ =
scrollable_view->AddChildView(std::make_unique<views::Separator>());
if (chromeos::features::IsJellyEnabled()) {
expanded_view_separator_->SetColorId(cros_tokens::kCrosSysSeparator);
}
expanded_view_separator_->SetProperty(views::kMarginsKey,
gfx::Insets::VH(0, kSpacing));
expanded_view_ =
scrollable_view->AddChildView(std::move(expanded_view_container));
// |expanded_view_| is not visible by default.
expanded_view_->SetVisible(false);
expanded_view_separator_->SetVisible(false);
}
return scrollable_view;
}
void SharesheetBubbleView::PopulateLayoutsWithTargets(
std::vector<TargetInfo> targets,
views::TableLayoutView* default_view,
views::TableLayoutView* expanded_view) {
// Add first kMaxRowsForDefaultView*kMaxTargetsPerRow targets to
// |default_view| and subsequent targets to |expanded_view|.
size_t row_count = 0;
size_t target_counter = 0;
auto* view_for_target = default_view;
for (auto& target : targets) {
if (target_counter % kMaxTargetsPerRow == 0) {
// When we've reached kMaxRowsForDefaultView switch to populating
// |expanded_layout|.
if (row_count == kMaxRowsForDefaultView) {
DCHECK(expanded_view);
view_for_target = expanded_view;
}
++row_count;
view_for_target->AddRows(1, views::TableLayout::kFixedSize);
}
++target_counter;
// Make a copy because value is needed after target is std::moved below.
std::u16string display_name = target.display_name;
std::u16string secondary_display_name =
target.secondary_display_name.value_or(std::u16string());
std::optional<gfx::ImageSkia> icon = target.icon;
view_for_target->AddChildView(std::make_unique<SharesheetTargetButton>(
base::BindRepeating(&SharesheetBubbleView::TargetButtonPressed,
base::Unretained(this), target),
display_name, secondary_display_name, icon,
delegator_->GetVectorIcon(display_name), target.is_dlp_blocked));
}
}
void SharesheetBubbleView::ShowActionView() {
close_on_deactivate_ = false;
constexpr float kShareActionScaleUpFactor = 0.9f;
main_view_->SetPaintToLayer();
ui::Layer* main_view_layer = main_view_->layer();
main_view_layer->SetFillsBoundsOpaquely(false);
main_view_layer->SetRoundedCornerRadius(gfx::RoundedCornersF(kCornerRadius));
// |main_view_| opacity fade out.
auto scoped_settings = std::make_unique<ui::ScopedLayerAnimationSettings>(
main_view_layer->GetAnimator());
scoped_settings->SetTransitionDuration(kQuickAnimateTime);
scoped_settings->SetTweenType(gfx::Tween::Type::LINEAR);
main_view_layer->SetOpacity(0.0f);
main_view_->SetVisible(false);
share_action_view_->SetPaintToLayer();
ui::Layer* share_action_view_layer = share_action_view_->layer();
share_action_view_layer->SetFillsBoundsOpaquely(false);
share_action_view_layer->SetRoundedCornerRadius(
gfx::RoundedCornersF(kCornerRadius));
share_action_view_->SetVisible(true);
share_action_view_layer->SetOpacity(0.0f);
gfx::Transform transform = gfx::GetScaleTransform(
gfx::Rect(share_action_view_layer->size()).CenterPoint(),
kShareActionScaleUpFactor);
share_action_view_layer->SetTransform(transform);
auto share_action_scoped_settings =
std::make_unique<ui::ScopedLayerAnimationSettings>(
share_action_view_layer->GetAnimator());
share_action_scoped_settings->SetPreemptionStrategy(
ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
// |share_action_view_| scale fade in.
share_action_scoped_settings->SetTransitionDuration(kSlowAnimateTime);
share_action_scoped_settings->SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN_2);
// Set##name kicks off the animation with the TransitionDuration and
// TweenType currently set. See ui/compositor/layer_animator.cc Set##name.
share_action_view_layer->SetTransform(gfx::Transform());
// |share_action_view_| opacity fade in.
share_action_scoped_settings->SetTransitionDuration(kQuickAnimateTime);
share_action_scoped_settings->SetTweenType(gfx::Tween::Type::LINEAR);
share_action_view_layer->SetOpacity(1.0f);
// Delay |share_action_view_| animate so that we can see |main_view_| fade out
// first.
share_action_view_layer->GetAnimator()->SchedulePauseForProperties(
kAnimateDelay, ui::LayerAnimationElement::TRANSFORM |
ui::LayerAnimationElement::OPACITY);
}
void SharesheetBubbleView::ResizeBubble(const int& width, const int& height) {
auto old_bounds = gfx::RectF(width_, height_);
width_ = width;
height_ = height;
// Animate from the old bubble to the new bubble.
ui::Layer* layer = View::GetWidget()->GetLayer();
const gfx::Transform transform =
gfx::TransformBetweenRects(old_bounds, gfx::RectF(width, height));
layer->SetTransform(transform);
auto scoped_settings =
std::make_unique<ui::ScopedLayerAnimationSettings>(layer->GetAnimator());
scoped_settings->SetTransitionDuration(kSlowAnimateTime);
scoped_settings->SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN_2);
layer->GetAnimator()->SchedulePauseForProperties(
kAnimateDelay, ui::LayerAnimationElement::TRANSFORM);
UpdateAnchorPosition();
layer->SetTransform(gfx::Transform());
}
// CloseBubble is called from a ShareAction or after an app launches.
void SharesheetBubbleView::CloseBubble(views::Widget::ClosedReason reason) {
CloseWidgetWithAnimateFadeOut(reason);
}
bool SharesheetBubbleView::AcceleratorPressed(
const ui::Accelerator& accelerator) {
// We override this because when this is handled by the base class,
// OnKeyPressed is not invoked when a user presses |VKEY_ESCAPE| if they have
// not pressed |VKEY_TAB| first to focus the SharesheetBubbleView.
DCHECK_EQ(accelerator.key_code(), ui::VKEY_ESCAPE);
if (share_action_view_->GetVisible() &&
delegator_->OnAcceleratorPressed(accelerator, active_target_)) {
return true;
}
// If the bubble is already in the process of closing, return early without
// doing anything.
if (is_bubble_closing_) {
return true;
}
// If delivered_callback_ is not null at this point, then the sharesheet was
// closed before a target was selected.
if (delivered_callback_) {
std::move(delivered_callback_).Run(::sharesheet::SharesheetResult::kCancel);
}
escape_pressed_ = true;
::sharesheet::SharesheetMetrics::RecordSharesheetActionMetrics(
::sharesheet::SharesheetMetrics::UserAction::kCancelledThroughEscPress);
CloseWidgetWithAnimateFadeOut(views::Widget::ClosedReason::kEscKeyPressed);
return true;
}
bool SharesheetBubbleView::OnKeyPressed(const ui::KeyEvent& event) {
// Ignore key press if it's not an arrow or bubble is closing.
if (!IsKeyboardCodeArrow(event.key_code()) || default_view_ == nullptr ||
is_bubble_closing_) {
if (event.key_code() == ui::VKEY_ESCAPE && !is_bubble_closing_) {
escape_pressed_ = true;
}
return false;
}
int delta = 0;
switch (event.key_code()) {
case ui::VKEY_UP:
delta = -kMaxTargetsPerRow;
break;
case ui::VKEY_DOWN:
delta = kMaxTargetsPerRow;
break;
case ui::VKEY_LEFT:
delta = base::i18n::IsRTL() ? 1 : -1;
break;
case ui::VKEY_RIGHT:
delta = base::i18n::IsRTL() ? -1 : 1;
break;
default:
NOTREACHED();
break;
}
const size_t default_views = default_view_->children().size();
auto* expanded_view_table =
show_expanded_view_ ? expanded_view_->children()[1].get() : nullptr;
const size_t targets =
default_views +
(show_expanded_view_ ? expanded_view_table->children().size() : 0);
const int new_target = static_cast<int>(keyboard_highlighted_target_) + delta;
keyboard_highlighted_target_ = static_cast<size_t>(
std::clamp(new_target, 0, static_cast<int>(targets) - 1));
if (keyboard_highlighted_target_ < default_views) {
default_view_->children()[keyboard_highlighted_target_]->RequestFocus();
} else {
expanded_view_table
->children()[keyboard_highlighted_target_ - default_views]
->RequestFocus();
}
return true;
}
std::unique_ptr<views::NonClientFrameView>
SharesheetBubbleView::CreateNonClientFrameView(views::Widget* widget) {
// TODO(crbug.com/1097623) Replace this with layer->SetRoundedCornerRadius.
auto bubble_border =
std::make_unique<views::BubbleBorder>(arrow(), GetShadow());
bubble_border->SetColor(color());
bubble_border->SetCornerRadius(kCornerRadius);
auto frame =
views::BubbleDialogDelegateView::CreateNonClientFrameView(widget);
static_cast<views::BubbleFrameView*>(frame.get())
->SetBubbleBorder(std::move(bubble_border));
return frame;
}
gfx::Size SharesheetBubbleView::CalculatePreferredSize() const {
return gfx::Size(width_, height_);
}
void SharesheetBubbleView::OnWidgetActivationChanged(views::Widget* widget,
bool active) {
// Catch widgets that are closing due to the user clicking out of the bubble.
// If |close_on_deactivate_| we should close the bubble here.
if (!active && close_on_deactivate_ && !is_bubble_closing_) {
if (delivered_callback_) {
std::move(delivered_callback_)
.Run(::sharesheet::SharesheetResult::kCancel);
}
auto user_action = ::sharesheet::SharesheetMetrics::UserAction::
kCancelledThroughClickingOut;
auto closed_reason = views::Widget::ClosedReason::kLostFocus;
if (escape_pressed_) {
user_action = ::sharesheet::SharesheetMetrics::UserAction::
kCancelledThroughEscPress;
closed_reason = views::Widget::ClosedReason::kEscKeyPressed;
}
::sharesheet::SharesheetMetrics::RecordSharesheetActionMetrics(user_action);
CloseWidgetWithAnimateFadeOut(closed_reason);
}
}
void SharesheetBubbleView::OnDisplayTabletStateChanged(
display::TabletState state) {
if (display::IsTabletStateChanging(state)) {
// Do nothing if the tablet state still in the process of transition.
return;
}
UpdateAnchorPosition();
}
void SharesheetBubbleView::InitBubble() {
// This disables the default deactivation behaviour in
// BubbleDialogDelegateView. Close on deactivation behaviour is managed by the
// SharesheetBubbleView with the |close_on_deactivate_| member.
set_close_on_deactivate(false);
SetButtons(ui::DIALOG_BUTTON_NONE);
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
// Margins must be set to 0 or share_action_view will have undesired margins.
set_margins(gfx::Insets());
auto main_view = std::make_unique<views::View>();
main_view_ = AddChildView(std::move(main_view));
auto share_action_view = std::make_unique<views::View>();
share_action_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
share_action_view_ = AddChildView(std::move(share_action_view));
share_action_view_->SetID(SHARE_ACTION_VIEW_ID);
share_action_view_->SetVisible(false);
}
void SharesheetBubbleView::SetUpAndShowBubble() {
main_view_->SetFocusBehavior(View::FocusBehavior::NEVER);
views::BubbleDialogDelegateView::CreateBubble(base::WrapUnique(this));
GetWidget()->GetRootView()->DeprecatedLayoutImmediately();
RecordMimeTypeMetric(intent_);
ShowWidgetWithAnimateFadeIn();
UpdateAnchorPosition();
display::Screen::GetScreen()->AddObserver(this);
}
void SharesheetBubbleView::ExpandButtonPressed() {
show_expanded_view_ = !show_expanded_view_;
// Scrollview has separators that overlaps with |header_body_separator_| and
// |body_footer_separator_| to create a double line when both are visible, so
// when scrollview is expanded we hide our separators.
if (header_body_separator_)
header_body_separator_->SetVisible(!show_expanded_view_);
body_footer_separator_->SetVisible(!show_expanded_view_);
expanded_view_->SetVisible(show_expanded_view_);
expanded_view_separator_->SetVisible(show_expanded_view_);
if (show_expanded_view_) {
body_view_->SetPreferredSize(
gfx::Size(body_view_->width(), kTargetViewExpandedHeight));
expand_button_->SetToExpandedState();
AnimateToExpandedState();
} else {
body_view_->SetPreferredSize(gfx::Size(
body_view_->width(), default_view_->GetPreferredSize().height()));
expand_button_->SetToDefaultState();
}
SizeToPreferredSize();
ResizeBubble(kDefaultBubbleWidth, main_view_->GetPreferredSize().height());
}
void SharesheetBubbleView::AnimateToExpandedState() {
expanded_view_->SetVisible(true);
expanded_view_->SetPaintToLayer();
ui::Layer* expanded_view_layer = expanded_view_->layer();
expanded_view_layer->SetFillsBoundsOpaquely(false);
expanded_view_layer->SetRoundedCornerRadius(
gfx::RoundedCornersF(kCornerRadius));
expanded_view_layer->SetOpacity(0.0f);
// |expanded_view_| opacity fade in.
auto scoped_settings = std::make_unique<ui::ScopedLayerAnimationSettings>(
expanded_view_layer->GetAnimator());
scoped_settings->SetTransitionDuration(kQuickAnimateTime);
scoped_settings->SetTweenType(gfx::Tween::Type::LINEAR);
expanded_view_layer->SetOpacity(1.0f);
}
void SharesheetBubbleView::TargetButtonPressed(TargetInfo target) {
if (!intent_) {
return;
}
auto type = target.type;
if (type == ::sharesheet::TargetType::kAction) {
active_target_ = target.launch_name;
} else {
intent_->activity_name = target.activity_name;
}
delegator_->OnTargetSelected(target.launch_name, type, std::move(intent_),
share_action_view_);
if (delivered_callback_) {
std::move(delivered_callback_)
.Run(::sharesheet::SharesheetResult::kSuccess);
}
}
void SharesheetBubbleView::UpdateAnchorPosition() {
// If |width_| is not set, set to default value.
if (width_ == 0) {
SetToDefaultBubbleSizing();
}
// Horizontally centered
int x_within_parent_view = parent_view_->GetMirroredXInView(
(parent_view_->bounds().width() - width_) / 2);
// Get position in screen, taking parent view origin into account. This is
// 0,0 in fullscreen on the primary display, but not on secondary displays, or
// in Hosted App windows.
gfx::Point origin = parent_view_->GetBoundsInScreen().origin();
origin += gfx::Vector2d(x_within_parent_view, kBubbleTopPaddingFromWindow);
// SetAnchorRect will CalculatePreferredSize when called.
SetAnchorRect(gfx::Rect(origin, gfx::Size()));
}
void SharesheetBubbleView::SetToDefaultBubbleSizing() {
width_ = kDefaultBubbleWidth;
height_ = main_view_->GetPreferredSize().height();
}
void SharesheetBubbleView::ShowWidgetWithAnimateFadeIn() {
constexpr float kSharesheetScaleUpFactor = 0.8f;
constexpr auto kSharesheetScaleUpTime = base::Milliseconds(150);
views::Widget* widget = View::GetWidget();
ui::Layer* layer = widget->GetLayer();
layer->SetOpacity(0.0f);
widget->ShowInactive();
gfx::Transform transform = gfx::GetScaleTransform(
gfx::Rect(layer->size()).CenterPoint(), kSharesheetScaleUpFactor);
layer->SetTransform(transform);
auto scoped_settings =
std::make_unique<ui::ScopedLayerAnimationSettings>(layer->GetAnimator());
scoped_settings->SetTransitionDuration(kSharesheetScaleUpTime);
scoped_settings->SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
layer->SetTransform(gfx::Transform());
scoped_settings->SetTransitionDuration(kQuickAnimateTime);
scoped_settings->SetTweenType(gfx::Tween::Type::LINEAR);
layer->SetOpacity(1.0f);
widget->Activate();
}
void SharesheetBubbleView::CloseWidgetWithAnimateFadeOut(
views::Widget::ClosedReason closed_reason) {
if (is_bubble_closing_) {
return;
}
// Don't attempt to react to tablet mode changes while the sharesheet is
// closing.
display::Screen::GetScreen()->RemoveObserver(this);
is_bubble_closing_ = true;
ui::Layer* layer = View::GetWidget()->GetLayer();
constexpr auto kSharesheetOpacityFadeOutTime = base::Milliseconds(80);
auto scoped_settings =
std::make_unique<ui::ScopedLayerAnimationSettings>(layer->GetAnimator());
scoped_settings->SetTweenType(gfx::Tween::Type::LINEAR);
scoped_settings->SetTransitionDuration(kSharesheetOpacityFadeOutTime);
// This aborts any running animations and starts the current one.
scoped_settings->SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
layer->SetOpacity(0.0f);
// We are closing the native widget during the close animation which results
// in destroying the layer and the animation and the observer not calling
// back. Thus it is safe to use base::Unretained here.
scoped_settings->AddObserver(new ui::ClosureAnimationObserver(
base::BindOnce(&SharesheetBubbleView::CloseWidgetWithReason,
base::Unretained(this), closed_reason)));
}
void SharesheetBubbleView::CloseWidgetWithReason(
views::Widget::ClosedReason closed_reason) {
View::GetWidget()->CloseWithReason(closed_reason);
// Run |close_callback_| after the widget closes.
if (close_callback_) {
std::move(close_callback_).Run(closed_reason);
}
// Bubble is deleted here.
delegator_->OnBubbleClosed(active_target_);
}
BEGIN_METADATA(SharesheetBubbleView)
END_METADATA
} // namespace sharesheet
} // namespace ash