// Copyright 2024 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/picker/views/picker_preview_bubble_controller.h"
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include "ash/picker/views/picker_preview_bubble.h"
#include "ash/public/cpp/holding_space/holding_space_image.h"
#include "base/check.h"
#include "base/files/file.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/observer_list.h"
#include "base/time/time.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
// Duration to wait before showing the preview bubble when it is requested.
constexpr base::TimeDelta kShowBubbleDelay = base::Milliseconds(600);
} // namespace
PickerPreviewBubbleController::PickerPreviewBubbleController() = default;
PickerPreviewBubbleController::~PickerPreviewBubbleController() {
CloseBubble();
}
void PickerPreviewBubbleController::ShowBubbleAfterDelay(
HoldingSpaceImage* async_preview_image,
const base::FilePath& path,
views::View* anchor_view) {
CreateBubbleWidget(
async_preview_image,
anchor_view);
show_bubble_timer_.Start(
FROM_HERE, kShowBubbleDelay,
base::BindOnce(&PickerPreviewBubbleController::ShowBubble,
weak_ptr_factory_.GetWeakPtr()));
}
void PickerPreviewBubbleController::CloseBubble() {
if (bubble_view_ == nullptr) {
return;
}
bubble_view_->Close();
OnWidgetDestroying(bubble_view_->GetWidget());
for (auto& observer : observers_) {
observer.OnPreviewBubbleVisibilityChanged(false);
}
}
bool PickerPreviewBubbleController::IsBubbleVisible() const {
return bubble_view_ != nullptr;
}
void PickerPreviewBubbleController::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void PickerPreviewBubbleController::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void PickerPreviewBubbleController::SetBubbleMainText(
const std::u16string& text) {
if (bubble_view_ == nullptr) {
return;
}
if (text.empty()) {
bubble_view_->ClearText();
} else {
bubble_view_->SetText(text);
}
}
void PickerPreviewBubbleController::OnWidgetDestroying(views::Widget* widget) {
widget_observation_.Reset();
bubble_view_ = nullptr;
async_preview_image_ = nullptr;
}
void PickerPreviewBubbleController::ShowBubbleImmediatelyForTesting(
HoldingSpaceImage* async_preview_image,
views::View* anchor_view) {
CreateBubbleWidget(async_preview_image, anchor_view);
bubble_view_->GetWidget()->Show();
}
PickerPreviewBubbleView*
PickerPreviewBubbleController::bubble_view_for_testing() const {
return bubble_view_;
}
void PickerPreviewBubbleController::UpdateBubbleImage() {
if (bubble_view_ != nullptr) {
bubble_view_->SetPreviewImage(
ui::ImageModel::FromImageSkia(async_preview_image_->GetImageSkia(
PickerPreviewBubbleView::kPreviewImageSize)));
}
}
void PickerPreviewBubbleController::CreateBubbleWidget(
HoldingSpaceImage* async_preview_image,
views::View* anchor_view) {
if (bubble_view_ != nullptr) {
return;
}
CHECK(anchor_view);
bubble_view_ = new PickerPreviewBubbleView(anchor_view);
async_preview_image_ = async_preview_image;
bubble_view_->SetPreviewImage(
ui::ImageModel::FromImageSkia(async_preview_image_->GetImageSkia()));
// base::Unretained is safe here since `image_subscription_` is a member.
// During destruction, `image_subscription_` will be destroyed before the
// other members, so the callback is guaranteed to be safe.
image_subscription_ = async_preview_image_->AddImageSkiaChangedCallback(
base::BindRepeating(&PickerPreviewBubbleController::UpdateBubbleImage,
base::Unretained(this)));
widget_observation_.Observe(bubble_view_->GetWidget());
}
void PickerPreviewBubbleController::ShowBubble() {
if (bubble_view_ == nullptr) {
return;
}
bubble_view_->GetWidget()->Show();
for (auto& observer : observers_) {
observer.OnPreviewBubbleVisibilityChanged(true);
}
}
} // namespace ash