Create a banner message widget
Based on our ux spec, we create this widget to let everyone can follow up our spec easily. Bug: 173184155 Test: Run robotest and apply this widget in Settings and see the ui Change-Id: I65ebf5f5d9c59e9e52bfb5ad0d8a003a9642303a
This commit is contained in:
@@ -53,6 +53,7 @@ java_defaults {
|
||||
"SettingsLibUtils",
|
||||
"SettingsLibEmergencyNumber",
|
||||
"SettingsLibTopIntroPreference",
|
||||
"SettingsLibBannerMessagePreference",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
13
packages/SettingsLib/BannerMessagePreference/Android.bp
Normal file
13
packages/SettingsLib/BannerMessagePreference/Android.bp
Normal file
@@ -0,0 +1,13 @@
|
||||
android_library {
|
||||
name: "SettingsLibBannerMessagePreference",
|
||||
|
||||
srcs: ["src/**/*.java"],
|
||||
resource_dirs: ["res"],
|
||||
|
||||
static_libs: [
|
||||
"androidx.preference_preference",
|
||||
],
|
||||
|
||||
sdk_version: "system_current",
|
||||
min_sdk_version: "21",
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2020 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.settingslib.widget">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21"/>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,26 @@
|
||||
<!--
|
||||
Copyright (C) 2020 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M1,21L12,2L23,21H1ZM19.53,19L12,5.99L4.47,19H19.53ZM11,16V18H13V16H11ZM11,10H13V14H11V10Z"
|
||||
android:fillColor="?android:attr/colorError"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2020 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="24dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_warning"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/banner_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/Banner.Text.Title"/>
|
||||
<TextView
|
||||
android:id="@+id/banner_summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/Banner.Text.Summary"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end">
|
||||
<Button
|
||||
android:id="@+id/banner_negative_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/banner_positive_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2020 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<style name="Banner.Text.Title"
|
||||
parent="@android:style/TextAppearance.Material.Subhead">
|
||||
<item name="android:textSize">16sp</item>
|
||||
<item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
|
||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="Banner.Text.Summary"
|
||||
parent="@*android:style/TextAppearance.DeviceDefault.Body1">
|
||||
<item name="android:textColor">?android:attr/textColorSecondary</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settingslib.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
/**
|
||||
* Banner message is a banner displaying important information (permission request, page error etc),
|
||||
* and provide actions for user to address. It requires a user action to be dismissed.
|
||||
*/
|
||||
public class BannerMessagePreference extends Preference {
|
||||
|
||||
private static final String TAG = "BannerPreference";
|
||||
private BannerMessagePreference.ButtonInfo mPositiveButtonInfo;
|
||||
private BannerMessagePreference.ButtonInfo mNegativeButtonInfo;
|
||||
|
||||
public BannerMessagePreference(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public BannerMessagePreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public BannerMessagePreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
public BannerMessagePreference(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
holder.setDividerAllowedAbove(true);
|
||||
holder.setDividerAllowedBelow(true);
|
||||
|
||||
mPositiveButtonInfo.mButton = (Button) holder.findViewById(R.id.banner_positive_btn);
|
||||
mNegativeButtonInfo.mButton = (Button) holder.findViewById(R.id.banner_negative_btn);
|
||||
|
||||
mPositiveButtonInfo.setUpButton();
|
||||
mNegativeButtonInfo.setUpButton();
|
||||
|
||||
final TextView titleView = (TextView) holder.findViewById(R.id.banner_title);
|
||||
final TextView summaryView = (TextView) holder.findViewById(R.id.banner_summary);
|
||||
|
||||
titleView.setText(getTitle());
|
||||
summaryView.setText(getSummary());
|
||||
}
|
||||
|
||||
private void init() {
|
||||
mPositiveButtonInfo = new BannerMessagePreference.ButtonInfo();
|
||||
mNegativeButtonInfo = new BannerMessagePreference.ButtonInfo();
|
||||
setSelectable(false);
|
||||
setLayoutResource(R.layout.banner_message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the visibility state of positive button.
|
||||
*/
|
||||
public BannerMessagePreference setPositiveButtonVisible(boolean isVisible) {
|
||||
if (isVisible != mPositiveButtonInfo.mIsVisible) {
|
||||
mPositiveButtonInfo.mIsVisible = isVisible;
|
||||
notifyChanged();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the visibility state of negative button.
|
||||
*/
|
||||
public BannerMessagePreference setNegativeButtonVisible(boolean isVisible) {
|
||||
if (isVisible != mNegativeButtonInfo.mIsVisible) {
|
||||
mNegativeButtonInfo.mIsVisible = isVisible;
|
||||
notifyChanged();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when positive button is clicked.
|
||||
*/
|
||||
public BannerMessagePreference setPositiveButtonOnClickListener(
|
||||
View.OnClickListener listener) {
|
||||
if (listener != mPositiveButtonInfo.mListener) {
|
||||
mPositiveButtonInfo.mListener = listener;
|
||||
notifyChanged();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when negative button is clicked.
|
||||
*/
|
||||
public BannerMessagePreference setNegativeButtonOnClickListener(
|
||||
View.OnClickListener listener) {
|
||||
if (listener != mNegativeButtonInfo.mListener) {
|
||||
mNegativeButtonInfo.mListener = listener;
|
||||
notifyChanged();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text to be displayed in positive button.
|
||||
*/
|
||||
public BannerMessagePreference setPositiveButtonText(@StringRes int textResId) {
|
||||
final String newText = getContext().getString(textResId);
|
||||
if (!TextUtils.equals(newText, mPositiveButtonInfo.mText)) {
|
||||
mPositiveButtonInfo.mText = newText;
|
||||
notifyChanged();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text to be displayed in negative button.
|
||||
*/
|
||||
public BannerMessagePreference setNegativeButtonText(@StringRes int textResId) {
|
||||
final String newText = getContext().getString(textResId);
|
||||
if (!TextUtils.equals(newText, mNegativeButtonInfo.mText)) {
|
||||
mNegativeButtonInfo.mText = newText;
|
||||
notifyChanged();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
static class ButtonInfo {
|
||||
private Button mButton;
|
||||
private CharSequence mText;
|
||||
private View.OnClickListener mListener;
|
||||
private boolean mIsVisible = true;
|
||||
|
||||
void setUpButton() {
|
||||
mButton.setText(mText);
|
||||
mButton.setOnClickListener(mListener);
|
||||
|
||||
if (shouldBeVisible()) {
|
||||
mButton.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, two buttons are visible.
|
||||
* If user didn't set a text for a button, then it should not be shown.
|
||||
*/
|
||||
private boolean shouldBeVisible() {
|
||||
return mIsVisible && (!TextUtils.isEmpty(mText));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settingslib.widget;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class BannerMessagePreferenceTest {
|
||||
|
||||
private Context mContext;
|
||||
private View mRootView;
|
||||
private BannerMessagePreference mBannerPreference;
|
||||
private PreferenceViewHolder mHolder;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = RuntimeEnvironment.application;
|
||||
mRootView = View.inflate(mContext, R.layout.banner_message, null /* parent */);
|
||||
mHolder = PreferenceViewHolder.createInstanceForTests(mRootView);
|
||||
mBannerPreference = new BannerMessagePreference(mContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBindViewHolder_shouldSetTitle() {
|
||||
mBannerPreference.setTitle("test");
|
||||
|
||||
mBannerPreference.onBindViewHolder(mHolder);
|
||||
|
||||
assertThat(((TextView) mRootView.findViewById(R.id.banner_title)).getText())
|
||||
.isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBindViewHolder_shouldSetSummary() {
|
||||
mBannerPreference.setSummary("test");
|
||||
|
||||
mBannerPreference.onBindViewHolder(mHolder);
|
||||
|
||||
assertThat(((TextView) mRootView.findViewById(R.id.banner_summary)).getText())
|
||||
.isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPositiveButtonText_shouldShowPositiveButton() {
|
||||
mBannerPreference.setPositiveButtonText(R.string.tts_settings_title);
|
||||
|
||||
mBannerPreference.onBindViewHolder(mHolder);
|
||||
|
||||
assertThat(((Button) mRootView.findViewById(R.id.banner_positive_btn)).getVisibility())
|
||||
.isEqualTo(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setNegativeButtonText_shouldShowNegativeButton() {
|
||||
mBannerPreference.setNegativeButtonText(R.string.tts_settings_title);
|
||||
|
||||
mBannerPreference.onBindViewHolder(mHolder);
|
||||
|
||||
assertThat(((Button) mRootView.findViewById(R.id.banner_negative_btn)).getVisibility())
|
||||
.isEqualTo(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withoutSetPositiveButtonText_shouldHidePositiveButton() {
|
||||
mBannerPreference.onBindViewHolder(mHolder);
|
||||
|
||||
assertThat(((Button) mRootView.findViewById(R.id.banner_positive_btn)).getVisibility())
|
||||
.isEqualTo(View.GONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withoutSetNegativeButtonText_shouldHideNegativeButton() {
|
||||
mBannerPreference.onBindViewHolder(mHolder);
|
||||
|
||||
assertThat(((Button) mRootView.findViewById(R.id.banner_negative_btn)).getVisibility())
|
||||
.isEqualTo(View.GONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPositiveButtonVisible_withTrue_shouldShowPositiveButton() {
|
||||
mBannerPreference.setPositiveButtonText(R.string.tts_settings_title);
|
||||
|
||||
mBannerPreference.setPositiveButtonVisible(true);
|
||||
mBannerPreference.onBindViewHolder(mHolder);
|
||||
|
||||
assertThat(((Button) mRootView.findViewById(R.id.banner_positive_btn)).getVisibility())
|
||||
.isEqualTo(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPositiveButtonVisible_withFalse_shouldHidePositiveButton() {
|
||||
mBannerPreference.setPositiveButtonText(R.string.tts_settings_title);
|
||||
|
||||
mBannerPreference.setPositiveButtonVisible(false);
|
||||
mBannerPreference.onBindViewHolder(mHolder);
|
||||
|
||||
assertThat(((Button) mRootView.findViewById(R.id.banner_positive_btn)).getVisibility())
|
||||
.isEqualTo(View.GONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setNegativeButtonVisible_withTrue_shouldShowNegativeButton() {
|
||||
mBannerPreference.setNegativeButtonText(R.string.tts_settings_title);
|
||||
|
||||
mBannerPreference.setNegativeButtonVisible(true);
|
||||
mBannerPreference.onBindViewHolder(mHolder);
|
||||
|
||||
assertThat(((Button) mRootView.findViewById(R.id.banner_negative_btn)).getVisibility())
|
||||
.isEqualTo(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setNegativeButtonVisible_withFalse_shouldHideNegativeButton() {
|
||||
mBannerPreference.setNegativeButtonText(R.string.tts_settings_title);
|
||||
|
||||
mBannerPreference.setNegativeButtonVisible(false);
|
||||
mBannerPreference.onBindViewHolder(mHolder);
|
||||
|
||||
assertThat(((Button) mRootView.findViewById(R.id.banner_negative_btn)).getVisibility())
|
||||
.isEqualTo(View.GONE);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user