Skip to content

Commit

Permalink
[Passwords] Support Inline Note Editing from Password Details Bubble
Browse files Browse the repository at this point in the history
This CL introduces the functionality of inline editing of notes in
the password details bubble.
It works by introducing two rows in the details view: one for displaying
the note (which is visible by default), and another for editing the
note (which is hidden by default).
Switching between edit and display modes works by hiding and showing
such rows accordingly.

Mocks: http://docs/presentation/d/1wcAel6Z0LndWDV4jiGzRtuLnawIdW-mgdabrt8JDVlg?resourcekey=0-j3j8SeZl-eq-mrV2NPPQrg#slide=id.g18f8bda647f_0_210

Screencast: http://go/scrcast/NTI5MTQ4MjQzMzk3ODM2OHxiYjExYWU0ZS0zMA

Bug: 1408790
Change-Id: I471399d45196a6c8692c458d486af1dce1ff9c33
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4236684
Commit-Queue: Mohamed Amir Yosef <mamir@chromium.org>
Reviewed-by: Peter Kasting <pkasting@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1103481}
  • Loading branch information
mohamedamir authored and Chromium LUCI CQ committed Feb 9, 2023
1 parent 213d544 commit c0c0d20
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 6 deletions.
101 changes: 96 additions & 5 deletions chrome/browser/ui/views/passwords/manage_passwords_view.cc
Expand Up @@ -28,6 +28,7 @@
#include "components/vector_icons/vector_icons.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/models/image_model.h"
#include "ui/base/ui_base_types.h"
#include "ui/gfx/favicon_size.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/image_button.h"
Expand All @@ -37,6 +38,7 @@
#include "ui/views/controls/label.h"
#include "ui/views/controls/separator.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/controls/textarea/textarea.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/layout/flex_layout_view.h"
Expand Down Expand Up @@ -181,6 +183,39 @@ std::unique_ptr<views::Label> CreateNoteLabel(
return note_label;
}

std::unique_ptr<views::View> CreateEditNoteRow(
const password_manager::PasswordForm& form,
views::Textarea** textarea) {
auto row = std::make_unique<views::FlexLayoutView>();
row->SetCollapseMargins(true);
row->SetDefault(
views::kMarginsKey,
gfx::Insets::VH(0, ChromeLayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_RELATED_CONTROL_HORIZONTAL)));
row->SetCrossAxisAlignment(views::LayoutAlignment::kStart);

// TODO(crbug.com/1408790): Use a different icon for the notes to match the
// mocks.
row->AddChildView(CreateWrappedView(CreateIconView(kAccountCircleIcon)));

*textarea = row->AddChildView(std::make_unique<views::Textarea>());
(*textarea)->SetText(
form.GetNoteWithEmptyUniqueDisplayName().value_or(std::u16string()));
// TODO(crbug.com/1382017): use internationalized string.
(*textarea)->SetAccessibleName(u"Password Note");
int line_height = views::style::GetLineHeight(views::style::CONTEXT_TEXTFIELD,
views::style::STYLE_PRIMARY);
(*textarea)->SetPreferredSize(
gfx::Size(0, kMaxLinesVisibleFromPasswordNote * line_height +
2 * ChromeLayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_CONTROL_VERTICAL_TEXT_PADDING)));
(*textarea)->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
views::MaximumFlexSizeRule::kUnbounded));
return row;
}

} // namespace

ManagePasswordsView::ManagePasswordsView(content::WebContents* web_contents,
Expand Down Expand Up @@ -242,6 +277,31 @@ void ManagePasswordsView::AddedToWidget() {
GetBubbleFrameView()->SetTitleView(CreatePasswordListTitleView());
}

bool ManagePasswordsView::Accept() {
// Accept button is only visible in the details page where a password is
// selected.
DCHECK(currently_selected_password_.has_value());
DCHECK(note_textarea_);
currently_selected_password_->SetNoteWithEmptyUniqueDisplayName(
note_textarea_->GetText());
// TODO(crbug.com/1408790): invoke the controller to update the note in the
// storage.
SwitchToDisplayMode();
// Return false such that the bubble doesn't get closed upon clicking the
// button.
return false;
}

bool ManagePasswordsView::Cancel() {
// Cancel button is only visible in the details page where a password is
// selected.
DCHECK(currently_selected_password_.has_value());
SwitchToDisplayMode();
// Return false such that the bubble doesn't get closed upon clicking the
// button.
return false;
}

std::unique_ptr<views::View> ManagePasswordsView::CreatePasswordListTitleView()
const {
const ChromeLayoutProvider* layout_provider = ChromeLayoutProvider::Get();
Expand Down Expand Up @@ -346,8 +406,7 @@ std::unique_ptr<views::View> ManagePasswordsView::CreatePasswordListView() {
return container_view;
}

std::unique_ptr<views::View> ManagePasswordsView::CreatePasswordDetailsView()
const {
std::unique_ptr<views::View> ManagePasswordsView::CreatePasswordDetailsView() {
DCHECK(currently_selected_password_.has_value());
auto container_view = std::make_unique<views::BoxLayoutView>();
container_view->SetOrientation(views::BoxLayout::Orientation::kVertical);
Expand All @@ -373,12 +432,19 @@ std::unique_ptr<views::View> ManagePasswordsView::CreatePasswordDetailsView()

// TODO(crbug.com/1408790): Use a different icon for the notes to match the
// mocks.
// TODO(crbug.com/1408790): Assign action to the note action button.
// TODO(crbug.com/1408790): use internationalized string for the note action
// button tooltip text.
container_view->AddChildView(CreateDetailsRow(
// Add two rows: one for displaying the note which is visible by default, and
// another to edit the note, which is hidden by default. Clicking the Edit
// icon next to the note row will hide the display row, and show the edit row.
display_note_row_ = container_view->AddChildView(CreateDetailsRow(
kAccountCircleIcon, CreateNoteLabel(*currently_selected_password_),
vector_icons::kEditIcon, u"Edit Note", views::Button::PressedCallback()));
vector_icons::kEditIcon, u"Edit Note",
base::BindRepeating(&ManagePasswordsView::SwitchToEditNoteMode,
base::Unretained(this))));
edit_note_row_ = container_view->AddChildView(
CreateEditNoteRow(*currently_selected_password_, &note_textarea_));
edit_note_row_->SetVisible(false);
return container_view;
}

Expand Down Expand Up @@ -427,15 +493,40 @@ void ManagePasswordsView::RecreateLayout() {
frame_view->SetTitleView(CreatePasswordDetailsTitleView());
frame_view->SetFootnoteView(nullptr);
page_container_->SwitchToPage(CreatePasswordDetailsView());
page_container_->SetProperty(
views::kMarginsKey,
gfx::Insets().set_bottom(ChromeLayoutProvider::Get()
->GetInsetsMetric(views::INSETS_DIALOG)
.bottom()));
} else {
frame_view->SetTitleView(CreatePasswordListTitleView());
frame_view->SetFootnoteView(CreateFooterView());
page_container_->SwitchToPage(CreatePasswordListView());
page_container_->SetProperty(views::kMarginsKey, gfx::Insets());
}
PreferredSizeChanged();
SizeToContents();
}

void ManagePasswordsView::SwitchToEditNoteMode() {
display_note_row_->SetVisible(false);
edit_note_row_->SetVisible(true);
SetButtons(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL);
// TODO(crbug.com/1408790): use internationalized string.
SetButtonLabel(ui::DIALOG_BUTTON_OK, u"Update");
PreferredSizeChanged();
SizeToContents();
DCHECK(note_textarea_);
note_textarea_->RequestFocus();
}

void ManagePasswordsView::SwitchToDisplayMode() {
display_note_row_->SetVisible(true);
edit_note_row_->SetVisible(false);
SetButtons(ui::DIALOG_BUTTON_NONE);
RecreateLayout();
}

void ManagePasswordsView::OnFaviconReady(const gfx::Image& favicon) {
if (!favicon.IsEmpty()) {
favicon_ = favicon;
Expand Down
14 changes: 13 additions & 1 deletion chrome/browser/ui/views/passwords/manage_passwords_view.h
Expand Up @@ -10,6 +10,9 @@
#include "components/password_manager/core/browser/password_form.h"

class PageSwitcherView;
namespace views {
class Textarea;
}

// A dialog for managing stored password and federated login information for a
// specific site. A user can see the details of the passwords, and edit the
Expand All @@ -30,10 +33,12 @@ class ManagePasswordsView : public PasswordBubbleViewBase {
const PasswordBubbleControllerBase* GetController() const override;
ui::ImageModel GetWindowIcon() override;
void AddedToWidget() override;
bool Cancel() override;
bool Accept() override;

std::unique_ptr<views::View> CreatePasswordListTitleView() const;
std::unique_ptr<views::View> CreatePasswordListView();
std::unique_ptr<views::View> CreatePasswordDetailsView() const;
std::unique_ptr<views::View> CreatePasswordDetailsView();
std::unique_ptr<views::View> CreatePasswordDetailsTitleView();
std::unique_ptr<views::View> CreateFooterView();

Expand All @@ -42,6 +47,10 @@ class ManagePasswordsView : public PasswordBubbleViewBase {
// `currently_selected_password_` isn't set.
void RecreateLayout();

void SwitchToEditNoteMode();

void SwitchToDisplayMode();

// Called when the favicon is loaded. If |favicon| isn't empty, it sets
// |favicon_| and invokes RecreateLayout().
void OnFaviconReady(const gfx::Image& favicon);
Expand All @@ -58,6 +67,9 @@ class ManagePasswordsView : public PasswordBubbleViewBase {
// currently selected password.
absl::optional<password_manager::PasswordForm> currently_selected_password_;

views::View* display_note_row_ = nullptr;
views::View* edit_note_row_ = nullptr;
views::Textarea* note_textarea_ = nullptr;
ItemsBubbleController controller_;
raw_ptr<PageSwitcherView> page_container_ = nullptr;
};
Expand Down

0 comments on commit c0c0d20

Please sign in to comment.