Commit 80efe6c7 authored by Reda Tawfik's avatar Reda Tawfik Committed by Commit Bot

[Android][Mfill] Implement AllPasswordsBottomSheetView

This Cl adds |AllPasswordsBottomSheetView| which implements
|BottomSheetContent| and chnge its visibility state from the controller.
Adds a junit test.

This Cl skips adding screenshots for string resources. The UI is in
early stages and taking a screenshot for it does not help for
translation.
Will be added when the UI gives helpful context.

Bug: 1104132
Skip-Translation-Screenshots-Check: True
Change-Id: If6be6f9dbea93489b69b367d4d997cec595fb7d1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2320171Reviewed-by: default avatarIoana Pandele <ioanap@chromium.org>
Reviewed-by: default avatarFriedrich [CET] <fhorschig@chromium.org>
Commit-Queue: Reda Tawfik <redatawfik@noogler.google.com>
Cr-Commit-Position: refs/heads/master@{#800517}
parent be0e5df4
......@@ -27,6 +27,7 @@ android_library("internal_java") {
"//components/browser_ui/widget/android:java",
"//components/embedder_support/android:util_java",
"//components/feature_engagement/public:public_java",
"//components/url_formatter/android:url_formatter_java",
"//content/public/android:content_java",
"//third_party/android_deps:android_support_v7_appcompat_java",
"//third_party/android_deps:androidx_annotation_annotation_java",
......@@ -50,6 +51,8 @@ android_library("internal_java") {
"java/src/org/chromium/chrome/browser/keyboard_accessory/all_passwords_bottom_sheet/AllPasswordsBottomSheetCoordinator.java",
"java/src/org/chromium/chrome/browser/keyboard_accessory/all_passwords_bottom_sheet/AllPasswordsBottomSheetMediator.java",
"java/src/org/chromium/chrome/browser/keyboard_accessory/all_passwords_bottom_sheet/AllPasswordsBottomSheetProperties.java",
"java/src/org/chromium/chrome/browser/keyboard_accessory/all_passwords_bottom_sheet/AllPasswordsBottomSheetView.java",
"java/src/org/chromium/chrome/browser/keyboard_accessory/all_passwords_bottom_sheet/AllPasswordsBottomSheetViewBinder.java",
"java/src/org/chromium/chrome/browser/keyboard_accessory/all_passwords_bottom_sheet/Credential.java",
"java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryCoordinator.java",
"java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryIPHUtils.java",
......@@ -103,6 +106,7 @@ android_resources("java_resources") {
"java/res/drawable/autofill_chip_inset.xml",
"java/res/drawable/ic_vpn_key_off.xml",
"java/res/layout/address_accessory_sheet.xml",
"java/res/layout/all_passwords_bottom_sheet.xml",
"java/res/layout/credit_card_accessory_sheet.xml",
"java/res/layout/keyboard_accessory.xml",
"java/res/layout/keyboard_accessory_action.xml",
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical"/>
\ No newline at end of file
......@@ -8,6 +8,7 @@ import android.content.Context;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
/**
* Creates the AllPasswordsBottomSheet. AllPasswordsBottomSheet uses a bottom sheet to let the
......@@ -15,8 +16,6 @@ import org.chromium.ui.modelutil.PropertyModel;
*/
class AllPasswordsBottomSheetCoordinator {
private final AllPasswordsBottomSheetMediator mMediator = new AllPasswordsBottomSheetMediator();
private final PropertyModel mModel =
AllPasswordsBottomSheetProperties.createDefaultModel(mMediator::onDismissed);
/**
* This delegate is called when the AllPasswordsBottomSheet is interacted with (e.g. dismissed
......@@ -29,8 +28,8 @@ class AllPasswordsBottomSheetCoordinator {
void onCredentialSelected(Credential credential);
/**
* Called when the user dismisses the AllPasswordsBottomSheet. Not called if a suggestion
* was selected.
* Called when the user dismisses the AllPasswordsBottomSheet or if the bottom sheet content
* failed to be shown.
*/
void onDismissed();
}
......@@ -39,11 +38,14 @@ class AllPasswordsBottomSheetCoordinator {
* Initializes the component.
* @param context A {@link Context} to create views and retrieve resources.
* @param sheetController A {@link BottomSheetController} used to show/hide the sheet.
* @param delegate A {@link Delegate} that handles dismiss events.
* @param delegate A {@link Delegate} that handles select and dismiss events.
*/
public void initialize(Context context, BottomSheetController sheetController,
AllPasswordsBottomSheetCoordinator.Delegate delegate) {
mMediator.initialize(delegate, mModel);
PropertyModel model =
AllPasswordsBottomSheetProperties.createDefaultModel(mMediator::onDismissed);
mMediator.initialize(delegate, model);
setUpModelChangeProcessor(model, new AllPasswordsBottomSheetView(context, sheetController));
}
/**
......@@ -53,4 +55,9 @@ class AllPasswordsBottomSheetCoordinator {
public void showCredentials(Credential[] credentials) {
mMediator.showCredentials(credentials);
}
static void setUpModelChangeProcessor(PropertyModel model, AllPasswordsBottomSheetView view) {
PropertyModelChangeProcessor.create(
model, view, AllPasswordsBottomSheetViewBinder::bindAllPasswordsBottomSheet);
}
}
\ No newline at end of file
......@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.keyboard_accessory.all_passwords_bottom_sheet;
import static org.chromium.chrome.browser.keyboard_accessory.all_passwords_bottom_sheet.AllPasswordsBottomSheetProperties.VISIBLE;
import org.chromium.ui.modelutil.PropertyModel;
/**
......@@ -22,11 +24,17 @@ class AllPasswordsBottomSheetMediator {
void showCredentials(Credential[] credentials) {
assert credentials != null;
// Temporary call to destroy native objects to avoid memory leak.
mDelegate.onDismissed();
mModel.set(VISIBLE, true);
}
void onCredentialSelected(Credential credential) {
mModel.set(VISIBLE, false);
mDelegate.onCredentialSelected(credential);
}
void onDismissed(Integer integer) {
if (!mModel.get(VISIBLE)) return; // Dismiss only if not dismissed yet.
mModel.set(VISIBLE, false);
mDelegate.onDismissed();
}
}
......@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.keyboard_accessory.all_passwords_bottom_shee
import org.chromium.base.Callback;
import org.chromium.ui.modelutil.ListModel;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
/**
......@@ -20,8 +21,10 @@ class AllPasswordsBottomSheetProperties {
static final PropertyModel.ReadableObjectPropertyKey<ListModel<ListItem>> SHEET_ITEMS =
new PropertyModel.ReadableObjectPropertyKey<>("sheet_items");
static final PropertyKey[] ALL_KEYS = {VISIBLE, DISMISS_HANDLER, SHEET_ITEMS};
static PropertyModel createDefaultModel(Callback<Integer> handler) {
return new PropertyModel.Builder(VISIBLE, SHEET_ITEMS, DISMISS_HANDLER)
return new PropertyModel.Builder(ALL_KEYS)
.with(VISIBLE, false)
.with(SHEET_ITEMS, new ListModel<>())
.with(DISMISS_HANDLER, handler)
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.keyboard_accessory.all_passwords_bottom_sheet;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import org.chromium.base.Callback;
import org.chromium.chrome.browser.keyboard_accessory.R;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
/**
* This class is responsible for rendering the bottom sheet which displays all credentials from any
* origin. It is a View in this Model-View-Controller component and doesn't inherit from a view but
* holds Android Views.
*/
class AllPasswordsBottomSheetView implements BottomSheetContent {
private final BottomSheetController mBottomSheetController;
private Callback<Integer> mDismissHandler;
private final RecyclerView mContentView;
private final BottomSheetObserver mBottomSheetObserver = new EmptyBottomSheetObserver() {
@Override
public void onSheetClosed(@BottomSheetController.StateChangeReason int reason) {
super.onSheetClosed(reason);
assert mDismissHandler != null;
mDismissHandler.onResult(reason);
mBottomSheetController.removeObserver(mBottomSheetObserver);
}
@Override
public void onSheetStateChanged(int newState) {
super.onSheetStateChanged(newState);
if (newState != BottomSheetController.SheetState.HIDDEN) return;
// This is a fail-safe for cases where onSheetClosed isn't triggered.
mDismissHandler.onResult(BottomSheetController.StateChangeReason.NONE);
mBottomSheetController.removeObserver(mBottomSheetObserver);
}
};
/**
* Constructs an AllPasswordsBottomSheetView which creates, modifies, and shows the bottom
* sheet.
* @param context A {@link Context} used to load resources and inflate the sheet.
* @param bottomSheetController The {@link BottomSheetController} used to show/hide the sheet.
*/
public AllPasswordsBottomSheetView(
Context context, BottomSheetController bottomSheetController) {
mBottomSheetController = bottomSheetController;
mContentView = (RecyclerView) LayoutInflater.from(context).inflate(
R.layout.all_passwords_bottom_sheet, null);
}
/**
* Sets a new listener that reacts to events like item selection or dismissal.
* @param dismissHandler A {@link Callback<Integer>}.
*/
void setDismissHandler(Callback<Integer> dismissHandler) {
mDismissHandler = dismissHandler;
}
/**
* If set to true, requests to show the bottom sheet. Otherwise, requests to hide the sheet.
* @param isVisible A boolean describing whether to show or hide the sheet.
*/
void setVisible(boolean isVisible) {
if (isVisible) {
mBottomSheetController.addObserver(mBottomSheetObserver);
if (!mBottomSheetController.requestShowContent(this, true)) {
assert (mDismissHandler != null);
mDismissHandler.onResult(BottomSheetController.StateChangeReason.NONE);
mBottomSheetController.removeObserver(mBottomSheetObserver);
}
} else {
mBottomSheetController.hideContent(this, true);
}
}
@Override
public View getContentView() {
return mContentView;
}
@Nullable
@Override
public View getToolbarView() {
return null;
}
@Override
public int getVerticalScrollOffset() {
return 0;
}
@Override
public void destroy() {
mBottomSheetController.removeObserver(mBottomSheetObserver);
}
@Override
public int getPriority() {
return BottomSheetContent.ContentPriority.HIGH;
}
@Override
public boolean hasCustomScrimLifecycle() {
return false;
}
@Override
public boolean swipeToDismissEnabled() {
return false;
}
@Override
public boolean skipHalfStateOnScrollingDown() {
return false;
}
@Override
public int getPeekHeight() {
return BottomSheetContent.HeightMode.DISABLED;
}
@Override
public float getHalfHeightRatio() {
return BottomSheetContent.HeightMode.DISABLED;
}
@Override
public boolean hideOnScroll() {
return false;
}
@Override
public int getSheetContentDescriptionStringId() {
return R.string.all_passwords_bottom_sheet_content_description;
}
@Override
public int getSheetHalfHeightAccessibilityStringId() {
return R.string.all_passwords_bottom_sheet_half_height;
}
@Override
public int getSheetFullHeightAccessibilityStringId() {
return R.string.all_passwords_bottom_sheet_full_height;
}
@Override
public int getSheetClosedAccessibilityStringId() {
return R.string.all_passwords_bottom_sheet_closed;
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.keyboard_accessory.all_passwords_bottom_sheet;
import static org.chromium.chrome.browser.keyboard_accessory.all_passwords_bottom_sheet.AllPasswordsBottomSheetProperties.DISMISS_HANDLER;
import static org.chromium.chrome.browser.keyboard_accessory.all_passwords_bottom_sheet.AllPasswordsBottomSheetProperties.VISIBLE;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
/**
* Provides functions that map {@link AllPasswordsBottomSheetProperties} changes in a {@link
* PropertyModel} to the suitable method in {@link AllPasswordsBottomSheetView}.
*/
class AllPasswordsBottomSheetViewBinder {
/**
* Called whenever a property in the given model changes. It updates the given view accordingly.
* @param model The observed {@link PropertyModel}. Its data need to be reflected in the view.
* @param view The {@link AllPasswordsBottomSheetView} to update.
* @param propertyKey The {@link PropertyKey} which changed.
*/
static void bindAllPasswordsBottomSheet(
PropertyModel model, AllPasswordsBottomSheetView view, PropertyKey propertyKey) {
if (propertyKey == DISMISS_HANDLER) {
view.setDismissHandler(model.get(DISMISS_HANDLER));
} else if (propertyKey == VISIBLE) {
view.setVisible(model.get(VISIBLE));
}
}
}
......@@ -168,6 +168,18 @@
</translations>
<release allow_pseudo="false" seq="1">
<messages fallback_to_english="true">
<message name="IDS_ALL_PASSWORDS_BOTTOM_SHEET_CONTENT_DESCRIPTION" desc="Accessibility string read when the all passwords bottom sheet is opened. It describes the bottom sheet where a user can pick a credential to fill into a password form.">
List of credentials to be filled on touch.
</message>
<message name="IDS_ALL_PASSWORDS_BOTTOM_SHEET_HALF_HEIGHT" desc="Accessibility string read when the all passwords bottom sheet showing a list of the user's credentials is opened at half height. The sheet will occupy the bottom half the screen.">
List of credentials to be filled on touch opened at half height.
</message>
<message name="IDS_ALL_PASSWORDS_BOTTOM_SHEET_FULL_HEIGHT" desc="Accessibility string read when the all passwords bottom sheet showing a list of the user's credentials is opened at full height. The sheet will occupy the entire screen.">
List of credentials to be filled on touch opened at full height.
</message>
<message name="IDS_ALL_PASSWORDS_BOTTOM_SHEET_CLOSED" desc="Accessibility string read when the all passwords bottom sheet showing a list of the user's credentials is closed.">
List of credentials to be filled on touch is closed.
</message>
<message name="IDS_AUTOFILL_KEYBOARD_ACCESSORY_CONTENT_DESCRIPTION" desc="The text announced by the screen reader when the password suggestions are shown.">
Passwords available
</message>
......
......@@ -33,15 +33,18 @@ import org.chromium.ui.modelutil.PropertyModel;
@Config(manifest = Config.NONE)
@Features.EnableFeatures(ChromeFeatureList.FILLING_PASSWORDS_FROM_ANY_ORIGIN)
public class AllPasswordsBottomSheetControllerTest {
private static final Credential[] TEST_CREDENTIALS = new Credential[] {
new Credential("user1", "password123", "user1", "origin.com", false, false)};
@Mock
private AllPasswordsBottomSheetCoordinator.Delegate mMockDelegate;
private final AllPasswordsBottomSheetMediator mMediator = new AllPasswordsBottomSheetMediator();
private AllPasswordsBottomSheetMediator mMediator;
private PropertyModel mModel;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mMediator = new AllPasswordsBottomSheetMediator();
mModel = AllPasswordsBottomSheetProperties.createDefaultModel(mMediator::onDismissed);
mMediator.initialize(mMockDelegate, mModel);
}
......@@ -54,8 +57,24 @@ public class AllPasswordsBottomSheetControllerTest {
}
@Test
public void testCallsDelegateOnDismiss() {
public void testShowCredentialsSetsVisibile() {
mMediator.showCredentials(TEST_CREDENTIALS);
assertThat(mModel.get(VISIBLE), is(true));
}
@Test
public void testOnCredentialSelected() {
mMediator.showCredentials(TEST_CREDENTIALS);
mMediator.onCredentialSelected(TEST_CREDENTIALS[0]);
assertThat(mModel.get(VISIBLE), is(false));
verify(mMockDelegate).onCredentialSelected(TEST_CREDENTIALS[0]);
}
@Test
public void testOnDismiss() {
mMediator.showCredentials(TEST_CREDENTIALS);
mMediator.onDismissed(BottomSheetController.StateChangeReason.BACK_PRESS);
assertThat(mModel.get(VISIBLE), is(false));
verify(mMockDelegate).onDismissed();
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment