Commit cc767670 authored by Wenyu Fu's avatar Wenyu Fu Committed by Commit Bot

[HomepageConversion] Make RadioButtonWithEditText UI widget

Creating RadioButtonWithEditText to achieve entering customized URL in a radio
button group. By extending RadioButonWithDescription, this new UI widget
shared most of the margin and style with the original button.

Bug: 1036470
Change-Id: I395120490303e4a2f150248c551a8f1e952c4c0a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1966355
Auto-Submit: Wenyu Fu <wenyufu@chromium.org>
Commit-Queue: Theresa  <twellington@chromium.org>
Reviewed-by: default avatarBrandon Wylie <wylieb@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#727840}
parent 2d488224
...@@ -29,14 +29,14 @@ ...@@ -29,14 +29,14 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
app:titleText="@string/sync_import_existing_data" /> app:primaryText="@string/sync_import_existing_data" />
<org.chromium.chrome.browser.ui.widget.RadioButtonWithDescription <org.chromium.chrome.browser.ui.widget.RadioButtonWithDescription
android:id="@+id/sync_keep_separate_choice" android:id="@+id/sync_keep_separate_choice"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
app:titleText="@string/sync_keep_existing_data_separate" /> app:primaryText="@string/sync_keep_existing_data_separate" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
app:titleText="@string/themes_system_default_title" app:primaryText="@string/themes_system_default_title"
app:descriptionText="@string/themes_system_default_summary" /> app:descriptionText="@string/themes_system_default_summary" />
<!-- override default padding top and bottom --> <!-- override default padding top and bottom -->
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
android:paddingTop="8dp" android:paddingTop="8dp"
android:paddingBottom="8dp" android:paddingBottom="8dp"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
app:titleText="@string/light_mode" /> app:primaryText="@string/light_mode" />
<org.chromium.chrome.browser.ui.widget.RadioButtonWithDescription <org.chromium.chrome.browser.ui.widget.RadioButtonWithDescription
android:id="@+id/dark" android:id="@+id/dark"
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
android:paddingTop="8dp" android:paddingTop="8dp"
android:paddingBottom="8dp" android:paddingBottom="8dp"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
app:titleText="@string/dark_mode" /> app:primaryText="@string/dark_mode" />
</org.chromium.chrome.browser.ui.widget.RadioButtonWithDescriptionLayout> </org.chromium.chrome.browser.ui.widget.RadioButtonWithDescriptionLayout>
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
app:titleText="@string/website_settings_category_allowed" /> app:primaryText="@string/website_settings_category_allowed" />
<org.chromium.chrome.browser.ui.widget.RadioButtonWithDescription <org.chromium.chrome.browser.ui.widget.RadioButtonWithDescription
android:id="@+id/ask" android:id="@+id/ask"
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
app:titleText="@string/website_settings_category_ask" /> app:primaryText="@string/website_settings_category_ask" />
<org.chromium.chrome.browser.ui.widget.RadioButtonWithDescription <org.chromium.chrome.browser.ui.widget.RadioButtonWithDescription
android:id="@+id/blocked" android:id="@+id/blocked"
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
app:titleText="@string/website_settings_category_blocked" /> app:primaryText="@string/website_settings_category_blocked" />
</org.chromium.chrome.browser.ui.widget.RadioButtonWithDescriptionLayout> </org.chromium.chrome.browser.ui.widget.RadioButtonWithDescriptionLayout>
</LinearLayout> </LinearLayout>
...@@ -22,6 +22,7 @@ android_library("java") { ...@@ -22,6 +22,7 @@ android_library("java") {
"java/src/org/chromium/chrome/browser/ui/widget/RadioButtonLayout.java", "java/src/org/chromium/chrome/browser/ui/widget/RadioButtonLayout.java",
"java/src/org/chromium/chrome/browser/ui/widget/RadioButtonWithDescription.java", "java/src/org/chromium/chrome/browser/ui/widget/RadioButtonWithDescription.java",
"java/src/org/chromium/chrome/browser/ui/widget/RadioButtonWithDescriptionLayout.java", "java/src/org/chromium/chrome/browser/ui/widget/RadioButtonWithDescriptionLayout.java",
"java/src/org/chromium/chrome/browser/ui/widget/RadioButtonWithEditText.java",
"java/src/org/chromium/chrome/browser/ui/widget/RoundedCornerImageView.java", "java/src/org/chromium/chrome/browser/ui/widget/RoundedCornerImageView.java",
"java/src/org/chromium/chrome/browser/ui/widget/TintedDrawable.java", "java/src/org/chromium/chrome/browser/ui/widget/TintedDrawable.java",
"java/src/org/chromium/chrome/browser/ui/widget/ViewResourceFrameLayout.java", "java/src/org/chromium/chrome/browser/ui/widget/ViewResourceFrameLayout.java",
...@@ -100,6 +101,7 @@ android_library("ui_widget_java_tests") { ...@@ -100,6 +101,7 @@ android_library("ui_widget_java_tests") {
"java/src/org/chromium/chrome/browser/ui/widget/PromoDialogTest.java", "java/src/org/chromium/chrome/browser/ui/widget/PromoDialogTest.java",
"java/src/org/chromium/chrome/browser/ui/widget/RadioButtonLayoutTest.java", "java/src/org/chromium/chrome/browser/ui/widget/RadioButtonLayoutTest.java",
"java/src/org/chromium/chrome/browser/ui/widget/RadioButtonWithDescriptionLayoutTest.java", "java/src/org/chromium/chrome/browser/ui/widget/RadioButtonWithDescriptionLayoutTest.java",
"java/src/org/chromium/chrome/browser/ui/widget/RadioButtonWithEditTextTest.java",
"java/src/org/chromium/chrome/browser/ui/widget/WrappingLayoutTest.java", "java/src/org/chromium/chrome/browser/ui/widget/WrappingLayoutTest.java",
"java/src/org/chromium/chrome/browser/ui/widget/highlight/ViewHighlighterTest.java", "java/src/org/chromium/chrome/browser/ui/widget/highlight/ViewHighlighterTest.java",
] ]
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
android:background="@null" /> android:background="@null" />
<TextView <TextView
android:id="@+id/title" android:id="@+id/primary"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_toEndOf="@id/radio_button" android:layout_toEndOf="@id/radio_button"
...@@ -29,8 +29,8 @@ ...@@ -29,8 +29,8 @@
android:id="@+id/description" android:id="@+id/description"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignStart="@id/title" android:layout_alignStart="@id/primary"
android:layout_below="@id/title" android:layout_below="@id/primary"
android:textAppearance="@style/TextAppearance.BlackHint2" android:textAppearance="@style/TextAppearance.BlackHint2"
android:visibility="gone" /> android:visibility="gone" />
</merge> </merge>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2019 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. -->
<!-- RadioButtonWithEditText extends RelativeLayout -->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<RadioButton
android:id="@+id/radio_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginEnd="16dp"
android:clickable="false"
android:focusable="false"
android:background="@null" />
<!-- Hint will be added programmatically so we're ignoring the lint warning. -->
<EditText
tools:ignore="LabelFor"
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/radio_button"
android:layout_centerVertical="true"
android:textAppearance="@style/TextAppearance.BlackTitle1"
android:inputType="text" />
<!-- This TextView is hidden if it has no text, so the initial visibility should be "gone". -->
<TextView
android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignStart="@id/edit_text"
android:layout_below="@id/edit_text"
android:textAppearance="@style/TextAppearance.BlackHint2"
android:visibility="gone" />
</merge>
...@@ -23,10 +23,15 @@ ...@@ -23,10 +23,15 @@
</declare-styleable> </declare-styleable>
<declare-styleable name="RadioButtonWithDescription"> <declare-styleable name="RadioButtonWithDescription">
<attr name="titleText" format="string" /> <attr name="primaryText" format="string" />
<attr name="descriptionText" format="string" /> <attr name="descriptionText" format="string" />
</declare-styleable> </declare-styleable>
<declare-styleable name="RadioButtonWithEditText">
<attr name="android:hint"/>
<attr name="android:inputType" />
</declare-styleable>
<declare-styleable name="RoundedCornerImageView"> <declare-styleable name="RoundedCornerImageView">
<attr name="cornerRadiusTopStart" format="dimension" /> <attr name="cornerRadiusTopStart" format="dimension" />
<attr name="cornerRadiusTopEnd" format="dimension" /> <attr name="cornerRadiusTopEnd" format="dimension" />
......
...@@ -17,10 +17,30 @@ import android.view.View.OnClickListener; ...@@ -17,10 +17,30 @@ import android.view.View.OnClickListener;
import android.widget.RadioButton; import android.widget.RadioButton;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import java.util.List; import java.util.List;
/** /**
* A RadioButton with a title and descriptive text to the right. * <p>
* A RadioButton with a primary and descriptive text to the right.
* The radio button is designed to be contained in a group, with {@link
* RadioButtonWithDescriptionLayout} as the parent view. By default, the object will be inflated
* from {@link R.layout.radio_button_with_description).
* </p>
*
* <p>
* The primary of the text and an optional description to be contained in the group may be set in
* XML. Sample declaration in XML:
* <pre> {@code
* <org.chromium.chrome.browser.ui.widget.RadioButtonWithDescription
* android:id="@+id/system_default"
* android:layout_width="match_parent"
* android:layout_height="wrap_content"
* android:background="?attr/selectableItemBackground"
* app:primaryText="@string/feature_foo_option_one"
* app:descriptionText="@string/feature_foo_option_one_description" />
* } </pre>
* </p>
*/ */
public class RadioButtonWithDescription extends RelativeLayout implements OnClickListener { public class RadioButtonWithDescription extends RelativeLayout implements OnClickListener {
/** /**
...@@ -35,8 +55,9 @@ public class RadioButtonWithDescription extends RelativeLayout implements OnClic ...@@ -35,8 +55,9 @@ public class RadioButtonWithDescription extends RelativeLayout implements OnClic
} }
private RadioButton mRadioButton; private RadioButton mRadioButton;
private TextView mTitle; private TextView mPrimary;
private TextView mDescription; private TextView mDescription;
private ButtonCheckedStateChangedListener mButtonCheckedStateChangedListener; private ButtonCheckedStateChangedListener mButtonCheckedStateChangedListener;
private List<RadioButtonWithDescription> mGroup; private List<RadioButtonWithDescription> mGroup;
...@@ -49,11 +70,9 @@ public class RadioButtonWithDescription extends RelativeLayout implements OnClic ...@@ -49,11 +70,9 @@ public class RadioButtonWithDescription extends RelativeLayout implements OnClic
*/ */
public RadioButtonWithDescription(Context context, AttributeSet attrs) { public RadioButtonWithDescription(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.radio_button_with_description, this, true); LayoutInflater.from(context).inflate(getLayoutResource(), this, true);
mRadioButton = (RadioButton) findViewById(R.id.radio_button); setViewsInternal();
mTitle = (TextView) findViewById(R.id.title);
mDescription = (TextView) findViewById(R.id.description);
if (attrs != null) applyAttributes(attrs); if (attrs != null) applyAttributes(attrs);
...@@ -76,12 +95,53 @@ public class RadioButtonWithDescription extends RelativeLayout implements OnClic ...@@ -76,12 +95,53 @@ public class RadioButtonWithDescription extends RelativeLayout implements OnClic
setFocusable(true); setFocusable(true);
} }
private void applyAttributes(AttributeSet attrs) { /**
* Set the view elements that included in xml internally.
*/
protected void setViewsInternal() {
mRadioButton = getRadioButtonView();
mPrimary = getPrimaryTextView();
mDescription = getDescriptionTextView();
}
/**
* @return The layout resource id used for inflating this {@link RadioButtonWithDescription}.
*/
protected int getLayoutResource() {
return R.layout.radio_button_with_description;
}
/**
* @return RadioButton View inside this {@link RadioButtonWithDescription}.
*/
protected RadioButton getRadioButtonView() {
return (RadioButton) findViewById(R.id.radio_button);
}
/**
* @return TextView displayed as primary inside this {@link RadioButtonWithDescription}.
*/
protected TextView getPrimaryTextView() {
return (TextView) findViewById(R.id.primary);
}
/**
* @return TextView displayed as description inside this {@link RadioButtonWithDescription}.
*/
protected TextView getDescriptionTextView() {
return (TextView) findViewById(R.id.description);
}
/**
* Apply the customized AttributeSet to current view.
* @param attrs AttributeSet that will be applied to current view.
*/
protected void applyAttributes(AttributeSet attrs) {
TypedArray a = getContext().getTheme().obtainStyledAttributes( TypedArray a = getContext().getTheme().obtainStyledAttributes(
attrs, R.styleable.RadioButtonWithDescription, 0, 0); attrs, R.styleable.RadioButtonWithDescription, 0, 0);
String titleText = a.getString(R.styleable.RadioButtonWithDescription_titleText); String primaryText = a.getString(R.styleable.RadioButtonWithDescription_primaryText);
if (titleText != null) mTitle.setText(titleText); if (primaryText != null) mPrimary.setText(primaryText);
String descriptionText = String descriptionText =
a.getString(R.styleable.RadioButtonWithDescription_descriptionText); a.getString(R.styleable.RadioButtonWithDescription_descriptionText);
...@@ -89,7 +149,7 @@ public class RadioButtonWithDescription extends RelativeLayout implements OnClic ...@@ -89,7 +149,7 @@ public class RadioButtonWithDescription extends RelativeLayout implements OnClic
mDescription.setText(descriptionText); mDescription.setText(descriptionText);
mDescription.setVisibility(View.VISIBLE); mDescription.setVisibility(View.VISIBLE);
} else { } else {
((LayoutParams) mTitle.getLayoutParams()).addRule(RelativeLayout.CENTER_VERTICAL); ((LayoutParams) mPrimary.getLayoutParams()).addRule(RelativeLayout.CENTER_VERTICAL);
} }
a.recycle(); a.recycle();
...@@ -111,17 +171,17 @@ public class RadioButtonWithDescription extends RelativeLayout implements OnClic ...@@ -111,17 +171,17 @@ public class RadioButtonWithDescription extends RelativeLayout implements OnClic
} }
/** /**
* Sets the text shown in the title section. * Sets the text shown in the primary section.
*/ */
public void setTitleText(CharSequence text) { public void setPrimaryText(CharSequence text) {
mTitle.setText(text); mPrimary.setText(text);
} }
/** /**
* @return The text shown in the title section. * @return The text shown in the primary section.
*/ */
public CharSequence getTitleText() { public CharSequence getPrimaryText() {
return mTitle.getText(); return mPrimary.getText();
} }
/** /**
...@@ -131,10 +191,10 @@ public class RadioButtonWithDescription extends RelativeLayout implements OnClic ...@@ -131,10 +191,10 @@ public class RadioButtonWithDescription extends RelativeLayout implements OnClic
mDescription.setText(text); mDescription.setText(text);
if (TextUtils.isEmpty(text)) { if (TextUtils.isEmpty(text)) {
((LayoutParams) mTitle.getLayoutParams()).addRule(RelativeLayout.CENTER_VERTICAL); ((LayoutParams) mPrimary.getLayoutParams()).addRule(RelativeLayout.CENTER_VERTICAL);
mDescription.setVisibility(View.GONE); mDescription.setVisibility(View.GONE);
} else { } else {
((LayoutParams) mTitle.getLayoutParams()).removeRule(RelativeLayout.CENTER_VERTICAL); ((LayoutParams) mPrimary.getLayoutParams()).removeRule(RelativeLayout.CENTER_VERTICAL);
mDescription.setVisibility(View.VISIBLE); mDescription.setVisibility(View.VISIBLE);
} }
} }
...@@ -187,11 +247,11 @@ public class RadioButtonWithDescription extends RelativeLayout implements OnClic ...@@ -187,11 +247,11 @@ public class RadioButtonWithDescription extends RelativeLayout implements OnClic
// LinearLayout (no id): // LinearLayout (no id):
// |-> RadioButtonWithDescription (id=sync_confirm_import_choice) // |-> RadioButtonWithDescription (id=sync_confirm_import_choice)
// | |-> RadioButton (id=radio_button) // | |-> RadioButton (id=radio_button)
// | |-> TextView (id=title) // | |-> TextView (id=primary)
// | \-> TextView (id=description) // | \-> TextView (id=description)
// \-> RadioButtonWithDescription (id=sync_keep_separate_choice) // \-> RadioButtonWithDescription (id=sync_keep_separate_choice)
// |-> RadioButton (id=radio_button) // |-> RadioButton (id=radio_button)
// |-> TextView (id=title) // |-> TextView (id=primary)
// \-> TextView (id=description) // \-> TextView (id=description)
// //
// This causes the automagic state saving and recovery to do the wrong thing and restore all // This causes the automagic state saving and recovery to do the wrong thing and restore all
......
...@@ -10,16 +10,15 @@ import android.view.View; ...@@ -10,16 +10,15 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.RadioGroup; import android.widget.RadioGroup;
import androidx.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* <p>
* Manages a group of exclusive RadioButtonWithDescriptions, automatically inserting a margin in * Manages a group of exclusive RadioButtonWithDescriptions, automatically inserting a margin in
* between the rows to prevent them from squishing together. Has the option to set an accessory view * between the rows to prevent them from squishing together. Has the option to set an accessory view
* on any given RadioButtonWithDescription. Only one accessory view per layout is supported. * on any given RadioButtonWithDescription. Only one accessory view per layout is supported.
* * <pre>
* ------------------------------------------------- * -------------------------------------------------
* | O | MESSAGE #1 | * | O | MESSAGE #1 |
* description_1 | * description_1 |
...@@ -27,41 +26,27 @@ import java.util.List; ...@@ -27,41 +26,27 @@ import java.util.List;
* | O | MESSAGE #N | * | O | MESSAGE #N |
* description_n | * description_n |
* ------------------------------------------------- * -------------------------------------------------
* </pre>
* </p>
*
* <p>
* To declare in XML, define a RadioButtonWithDescriptionLayout that contains
* RadioButtonWithDescription and/or RadioButtonWithEditText children.
* For example:
* <pre>{@code
* <org.chromium.chrome.browser.ui.widget.RadioButtonWithDescriptionLayout
* android:layout_width="match_parent"
* android:layout_height="match_parent" >
* <org.chromium.chrome.browser.ui.widget.RadioButtonWithDescription
* ... />
* <org.chromium.chrome.browser.ui.widget.RadioButtonWithEditText
* ... />
* </org.chromium.chrome.browser.ui.widget.RadioButtonWithDescriptionLayout>
* }</pre>
* </p>
*/ */
public final class RadioButtonWithDescriptionLayout public final class RadioButtonWithDescriptionLayout
extends RadioGroup implements RadioButtonWithDescription.ButtonCheckedStateChangedListener { extends RadioGroup implements RadioButtonWithDescription.ButtonCheckedStateChangedListener {
/** Encapsulates information required to build a layout. */
public static class Option {
private final CharSequence mTitle;
private final CharSequence mDescription;
private final Object mTag;
/**
* @param title The title for this option. This will be displayed as the main text for the
* checkbox.
* @param description The description for this option. This will be displayed as the subtext
* under the main text for the checkbox.
* @param tag The tag for this option. This can be used to identify the checkbox in event
* listeners.
*/
public Option(CharSequence title, CharSequence description, @Nullable Object tag) {
mTitle = title;
mDescription = description;
mTag = tag;
}
public CharSequence getTitle() {
return mTitle;
}
public CharSequence getDescription() {
return mDescription;
}
public Object getTag() {
return mTag;
}
}
private final int mMarginBetweenRows; private final int mMarginBetweenRows;
private final List<RadioButtonWithDescription> mRadioButtonsWithDescriptions; private final List<RadioButtonWithDescription> mRadioButtonsWithDescriptions;
...@@ -110,24 +95,14 @@ public final class RadioButtonWithDescriptionLayout ...@@ -110,24 +95,14 @@ public final class RadioButtonWithDescriptionLayout
} }
/** /**
* Given a set of {@link Option} creates a set of {@link RadioButtonWithDescription}. When lists * Add group of {@link RadioButtonWithDescription} into current layout. For buttons that already
* are built this way, there are two options for getting the checkbox you want in an event * exist in other radio button group, the radio button group will be transferred into the group
* listener callback. * inside current layout.
* 1. Set a tag on the Option and use that with {@link View#findViewWithTag). * @param buttons List of {@link RadioButtonWithDescription} to add to this group.
* 2. Use the id passed through to {@link RadioGroup.OnCheckedChangeListener#onCheckedChanged}.
* with {@link View#findViewById}.
*
* @param options List of options to add to this group.
*/ */
public void addOptions(List<Option> options) { public void addButtons(List<RadioButtonWithDescription> buttons) {
for (Option option : options) { for (RadioButtonWithDescription b : buttons) {
RadioButtonWithDescription b = new RadioButtonWithDescription(getContext(), null);
b.setTitleText(option.getTitle());
b.setDescriptionText(option.getDescription());
b.setTag(option.getTag());
setupButton(b); setupButton(b);
addView(b, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); addView(b, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
} }
......
// Copyright 2019 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.ui.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.widget.EditText;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* A radio button that contains a text edit box. The text value inside the entry box could used to
* represent the value when this radio button is selected. This class also supports the
* functionality of adding a description the same as {@link RadioButtonWithDescription}.
*
* By default, this class is inflated from {@link R.layout.radio_button_with_edit_text}.
* </p>
*
* <p>
* There is no default hint provided in the EditText. User could set the hint message through {@link
* #setHint} API, or through android:hint attribute in xml definition.
* </p>
*
* <p>
* This class also provides an interface {@link RadioButtonWithEditText.OnLongClickListener} to
* observe the text changing in its entry box. To use, implement the interface {@link
* RadioButtonWithEditText.OnLongClickListener} and call {@link
* RadioButtonWithEditText#addTextChangeListener(OnTextChangeListener)} to start listening to
* changes in the EditText.
* </p>
*
* <p>
* The input type, text, hint message of EditText box and an optional description to be contained in
* the group may be set in XML. Sample declaration in XML:
* <pre>{@code
* <org.chromium.chrome.browser.ui.widget.RadioButtonWithEditText
* android:id="@+id/system_default"
* android:layout_width="match_parent"
* android:layout_height="wrap_content"
* android:background="?attr/selectableItemBackground"
* android:inputType="text"
* android:hint="@string/hint_text_bar"
* app:primaryText="@string/feature_foo_option_one"
* app:descriptionText="@string/feature_foo_option_one_description" />
* }</pre>
* </p>
*/
public class RadioButtonWithEditText extends RadioButtonWithDescription {
/**
* Interface that will subscribe to changes to the text inside {@link RadioButtonWithEditText}.
*
*/
public interface OnTextChangeListener {
/**
* Will be called when the EditText has a value change.
* @param newText The updated text in EditText.
*/
void onTextChanged(CharSequence newText);
}
private EditText mEditText;
private List<OnTextChangeListener> mListeners;
public RadioButtonWithEditText(Context context, AttributeSet attrs) {
super(context, attrs);
mListeners = new ArrayList<>();
}
@Override
protected int getLayoutResource() {
return R.layout.radio_button_with_edit_text;
}
@Override
protected void setViewsInternal() {
super.setViewsInternal();
mEditText = (EditText) getPrimaryTextView();
mEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
// Text set through AttributionSet will trigger this function before mListeners
// initialize, so we only notify listeners after initialization.
if (mListeners == null) return;
for (OnTextChangeListener listener : mListeners) {
listener.onTextChanged(s);
}
}
});
}
@Override
protected TextView getPrimaryTextView() {
return findViewById(R.id.edit_text);
}
@Override
protected void applyAttributes(AttributeSet attrs) {
super.applyAttributes(attrs);
TypedArray a = getContext().getTheme().obtainStyledAttributes(
attrs, R.styleable.RadioButtonWithEditText, 0, 0);
String hint = a.getString(R.styleable.RadioButtonWithEditText_android_hint);
if (hint != null) setHint(hint);
int inputType = a.getInt(
R.styleable.RadioButtonWithEditText_android_inputType, InputType.TYPE_CLASS_TEXT);
setInputType(inputType);
a.recycle();
}
/**
* Add a listener that will be notified when text inside this url has been changed
* @param listener New listener that will be notified when text edit has been changed
*/
public void addTextChangeListener(OnTextChangeListener listener) {
mListeners.add(listener);
}
/**
* Remove the listener from the subscription list
* @param listener Listener that will no longer listening to text edit changes
*/
public void removeTextChangeListener(OnTextChangeListener listener) {
mListeners.remove(listener);
}
/**
* Set the input type of text editor
* @param inputType An input type from {@link android.text.InputType}
*/
public void setInputType(int inputType) {
mEditText.setInputType(inputType);
}
/**
* Set the hint message of text edit box
*/
public void setHint(CharSequence hint) {
mEditText.setHint(hint);
}
/**
* Set the hint message of text edit box using pre-defined string from {@link R.string}
*/
public void setHint(int hintId) {
mEditText.setHint(hintId);
}
}
// Copyright 2019 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.ui.widget;
import android.app.Activity;
import android.support.test.filters.SmallTest;
import android.text.InputType;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.TextView;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.chrome.browser.ui.widget.test.R;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.test.util.DummyUiActivityTestCase;
/**
* Unit tests for {@link RadioButtonWithEditText}.
*/
@RunWith(BaseJUnit4ClassRunner.class)
public class RadioButtonWithEditTextTest extends DummyUiActivityTestCase {
private class TestListener implements RadioButtonWithEditText.OnTextChangeListener {
private CharSequence mCurrentText;
private int mNumberOfTimesTextChanged;
TestListener() {
mNumberOfTimesTextChanged = 0;
}
/**
* Will be called when the text edit has a value change.
*/
@Override
public void onTextChanged(CharSequence newText) {
mCurrentText = newText;
mNumberOfTimesTextChanged += 1;
}
void setCurrentText(CharSequence currentText) {
mCurrentText = currentText;
}
/**
* Get the current text stored inside
* @return current text updated by RadioButtonWithEditText
*/
CharSequence getCurrentText() {
return mCurrentText;
}
int getTimesCalled() {
return mNumberOfTimesTextChanged;
}
}
private TestListener mListener;
private Activity mActivity;
private RadioButtonWithEditText mRadioButtonWithEditText;
private RadioButton mButton;
private EditText mEditText;
@Override
public void setUpTest() throws Exception {
super.setUpTest();
mActivity = getActivity();
mListener = new TestListener();
setupViewsForTest();
}
private void setupViewsForTest() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
View layout = LayoutInflater.from(mActivity).inflate(
R.layout.radio_button_with_edit_text_test, null, false);
mActivity.setContentView(layout);
mRadioButtonWithEditText =
(RadioButtonWithEditText) layout.findViewById(R.id.test_radio_button);
Assert.assertNotNull(mRadioButtonWithEditText);
mButton = layout.findViewById(R.id.radio_button);
mEditText = layout.findViewById(R.id.edit_text);
Assert.assertNotNull("Radio Button should not be null", mButton);
Assert.assertNotNull("Edit Text should not be null", mEditText);
});
}
@Test
@SmallTest
public void testViewSetup() {
Assert.assertFalse("Button should not be set checked after init.", mButton.isChecked());
Assert.assertTrue(
"Text entry should be empty after init.", TextUtils.isEmpty(mEditText.getText()));
// Test if apply attr works
int textUriInputType = InputType.TYPE_TEXT_VARIATION_URI | InputType.TYPE_CLASS_TEXT;
Assert.assertEquals("EditText input type is different than attr setting.", textUriInputType,
mEditText.getInputType());
Assert.assertEquals("EditText input hint is different than attr setting.",
mActivity.getResources().getString(R.string.test_uri), mEditText.getHint());
TextView description = mActivity.findViewById(R.id.description);
Assert.assertNotNull("Description should not be null", description);
Assert.assertEquals("Description is different than attr setting.",
mActivity.getResources().getString(R.string.test_string), description.getText());
}
@Test
@SmallTest
public void testSetHint() {
final CharSequence hintMsg = "Text hint";
final String resourceString = mActivity.getResources().getString(R.string.test_string);
TestThreadUtils.runOnUiThreadBlocking(() -> {
mRadioButtonWithEditText.setHint(hintMsg);
Assert.assertEquals("Hint message set from string is different from test setting",
hintMsg.toString(), mEditText.getHint().toString());
mRadioButtonWithEditText.setHint(R.string.test_string);
Assert.assertEquals("Hint message set from resource id is different from test setting",
resourceString, mEditText.getHint().toString());
});
}
@Test
@SmallTest
public void testSetInputType() {
int[] commonInputTypes = {
InputType.TYPE_CLASS_DATETIME,
InputType.TYPE_CLASS_NUMBER,
InputType.TYPE_CLASS_PHONE,
InputType.TYPE_CLASS_TEXT,
InputType.TYPE_TEXT_VARIATION_URI,
InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS,
InputType.TYPE_DATETIME_VARIATION_DATE,
};
for (int type : commonInputTypes) {
mRadioButtonWithEditText.setInputType(type);
Assert.assertEquals(mEditText.getInputType(), type);
}
}
@Test
@SmallTest
public void testChangeEditText() {
final CharSequence str1 = "First string";
final CharSequence str2 = "SeConD sTrINg";
CharSequence origStr = mRadioButtonWithEditText.getPrimaryText();
// Test if changing the text edit will result in changing of listener
mRadioButtonWithEditText.addTextChangeListener(mListener);
mListener.setCurrentText(origStr);
int timesCalled = mListener.getTimesCalled();
// Test changes for edit text
TestThreadUtils.runOnUiThreadBlocking(
() -> { mRadioButtonWithEditText.setPrimaryText(str1); });
Assert.assertEquals("New String value should be updated", str1.toString(),
mRadioButtonWithEditText.getPrimaryText().toString());
Assert.assertEquals("Text message in listener should be updated accordingly",
str1.toString(), mListener.getCurrentText().toString());
Assert.assertEquals("TestListener#OnTextChanged should be called once", timesCalled + 1,
mListener.getTimesCalled());
// change to another text from View
timesCalled = mListener.getTimesCalled();
TestThreadUtils.runOnUiThreadBlocking(() -> { mEditText.setText(str2); });
Assert.assertEquals("New String value should be updated", str2.toString(),
mRadioButtonWithEditText.getPrimaryText().toString());
Assert.assertEquals("Text message in listener should be updated accordingly",
str2.toString(), mListener.getCurrentText().toString());
Assert.assertEquals("TestListener#OnTextChanged should be called once", timesCalled + 1,
mListener.getTimesCalled());
// change to another text from View
mRadioButtonWithEditText.removeTextChangeListener(mListener);
timesCalled = mListener.getTimesCalled();
TestThreadUtils.runOnUiThreadBlocking(
() -> { mRadioButtonWithEditText.setPrimaryText(str1); });
Assert.assertEquals("New String value should be updated", str1.toString(),
mRadioButtonWithEditText.getPrimaryText().toString());
Assert.assertEquals("Text message in listener should not be updated.", str2.toString(),
mListener.getCurrentText().toString());
Assert.assertEquals("TestListener#OnTextChanged should not be called any more", timesCalled,
mListener.getTimesCalled());
}
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2019 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. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<org.chromium.chrome.browser.ui.widget.RadioButtonWithDescriptionLayout
android:id="@+id/test_radio_button_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- RadioButtonWithDescription - With primary, without description. -->
<org.chromium.chrome.browser.ui.widget.RadioButtonWithDescription
android:id="@+id/test_radio_description_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:primaryText="@string/test_string" />
<!-- RadioButtonWithDescription - With primary and description. -->
<org.chromium.chrome.browser.ui.widget.RadioButtonWithDescription
android:id="@+id/test_radio_description_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:primaryText="@string/test_string"
app:descriptionText="@string/test_string" />
<!-- RadioButtonWithDescription - With primary, without description. -->
<org.chromium.chrome.browser.ui.widget.RadioButtonWithEditText
android:id="@+id/test_radio_edit_text_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/min_touch_target_size"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:inputType="text"
android:hint="@string/test_uri"
app:primaryText="@string/test_string" />
<!-- RadioButtonWithDescription - With primary and description. -->
<org.chromium.chrome.browser.ui.widget.RadioButtonWithEditText
android:id="@+id/test_radio_edit_text_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/min_touch_target_size"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:inputType="text"
android:hint="@string/test_uri"
app:primaryText="@string/test_string"
app:descriptionText="@string/test_string" />
</org.chromium.chrome.browser.ui.widget.RadioButtonWithDescriptionLayout>
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2019 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. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<org.chromium.chrome.browser.ui.widget.RadioButtonWithEditText
android:id="@+id/test_radio_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/min_touch_target_size"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:inputType="textUri"
android:hint="@string/test_uri"
android:background="?attr/selectableItemBackground"
app:descriptionText="@string/test_string" />
</FrameLayout>
...@@ -9,4 +9,7 @@ ...@@ -9,4 +9,7 @@
<string name="promo_dialog_test_primary_button">OK</string> <string name="promo_dialog_test_primary_button">OK</string>
<string name="promo_dialog_test_secondary_button">Cancel</string> <string name="promo_dialog_test_secondary_button">Cancel</string>
<string name="promo_dialog_test_footer">Learn more</string> <string name="promo_dialog_test_footer">Learn more</string>
<string name="test_string">A String used for tests</string>
<string name="test_uri">https://www.example.com</string>
</resources> </resources>
\ No newline at end of file
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