Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b1e28233 authored by Matías Hernández's avatar Matías Hernández
Browse files

Use TextInputLayout for the name field in the create/rename mode page

In addition to looking nicer, this fixes two accessibility issues ("no hint when text is entered" and "reason for disabled button is unclear").

This requires a bit of theme merging black magic, because TextInputLayout requires a Theme.AppCompat descendant, which the Settings theme isn't.

Fixes: 356398157
Fixes: 370654542
Fixes: 369942776
Test: atest ZenModeEditNamePreferenceControllerTest
Flag: android.app.modes_ui
Change-Id: I92d8ec044dabc6daed5d755e206120ec7abc143e
parent fd84f120
Loading
Loading
Loading
Loading
+26 −12
Original line number Diff line number Diff line
@@ -15,23 +15,37 @@
  limitations under the License.
  -->

<!-- Theme.AppCompat.DayNight is in the parent View so that it's merged with the Theme.Settings
     theme below. An AppCompat descendant (which Theme.Settings isn't is necessary to inflate
     TextInputLayout. -->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:theme="@style/Theme.AppCompat.DayNight"
    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
    android:paddingBottom="8dp">
    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">

    <EditText
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/edit_input_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/Theme.Settings"
        style="?attr/textInputFilledStyle"
        app:endIconMode="clear_text"
        app:errorEnabled="true"
        android:hint="@string/zen_mode_edit_name_hint">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@android:id/edit"
        android:minHeight="48dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:maxLines="1"
            android:inputType="text|textCapSentences"
            android:imeOptions="actionDone"
        android:selectAllOnFocus="true"
        android:hint="@string/zen_mode_edit_name_hint" />
            android:selectAllOnFocus="true" />

    </com.google.android.material.textfield.TextInputLayout>

</LinearLayout>
+3 −0
Original line number Diff line number Diff line
@@ -9607,6 +9607,9 @@
    <!-- Modes: Hint for the EditText for editing a mode's name [CHAR LIMIT=30] -->
    <string name="zen_mode_edit_name_hint">Mode name</string>
    <!-- Modes: Error message when editing a mode's name and the name is empty [CHAR LIMIT=40] -->
    <string name="zen_mode_edit_name_empty_error">Mode name cannot be empty</string>
    <!-- Modes: Text shown above the list of icons in the mode editor. [CHAR LIMIT=40] -->
    <string name="zen_mode_edit_choose_icon_title">Choose an icon</string>
+3 −0
Original line number Diff line number Diff line
@@ -73,6 +73,9 @@
        <item name="notification_importance_button_background_color_selected">?androidprv:attr/materialColorSecondaryContainer</item>
        <item name="notification_importance_button_border_color_selected">?androidprv:attr/materialColorOnSecondaryContainer</item>
        <item name="notification_importance_button_foreground_color_selected">?androidprv:attr/materialColorOnSecondaryContainer</item>

        <!-- For AppCompat widgets, e.g. TextInputLayout -->
        <item name="colorAccent">?android:attr/colorAccent</item>
    </style>

    <!-- Variant of the settings theme with no action bar. -->
+23 −3
Original line number Diff line number Diff line
@@ -17,9 +17,11 @@
package com.android.settings.notification.modes;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import android.content.Context;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.widget.EditText;

@@ -28,14 +30,18 @@ import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.widget.LayoutPreference;

import com.google.android.material.textfield.TextInputLayout;

import java.util.function.Consumer;

class ZenModeEditNamePreferenceController extends AbstractZenModePreferenceController {

    private final Consumer<String> mModeNameSetter;
    @Nullable private TextInputLayout mInputLayout;
    @Nullable private EditText mEditText;
    private boolean mIsSettingText;

@@ -50,7 +56,8 @@ class ZenModeEditNamePreferenceController extends AbstractZenModePreferenceContr
        super.displayPreference(screen);
        if (mEditText == null) {
            LayoutPreference pref = checkNotNull(screen.findPreference(getPreferenceKey()));
            mEditText = pref.findViewById(android.R.id.edit);
            mInputLayout = checkNotNull(pref.findViewById(R.id.edit_input_layout));
            mEditText = checkNotNull(pref.findViewById(android.R.id.edit));

            mEditText.addTextChangedListener(new TextWatcher() {
                @Override
@@ -61,9 +68,11 @@ class ZenModeEditNamePreferenceController extends AbstractZenModePreferenceContr

                @Override
                public void afterTextChanged(Editable s) {
                    if (!mIsSettingText) {
                        mModeNameSetter.accept(s.toString());
                    if (mIsSettingText) {
                        return;
                    }
                    mModeNameSetter.accept(s.toString());
                    updateErrorState(s.toString());
                }
            });
        }
@@ -79,9 +88,20 @@ class ZenModeEditNamePreferenceController extends AbstractZenModePreferenceContr
                if (!modeName.equals(currentText)) {
                    mEditText.setText(modeName);
                }
                updateErrorState(modeName);
            } finally {
                mIsSettingText = false;
            }
        }
    }

    private void updateErrorState(String currentName) {
        checkState(mInputLayout != null);
        if (TextUtils.isEmpty(currentName)) {
            mInputLayout.setError(
                    mContext.getString(R.string.zen_mode_edit_name_empty_error));
        } else {
            mInputLayout.setError(null);
        }
    }
}
+21 −2
Original line number Diff line number Diff line
@@ -35,6 +35,8 @@ import com.android.settingslib.notification.modes.TestModeBuilder;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.widget.LayoutPreference;

import com.google.android.material.textfield.TextInputLayout;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -55,6 +57,7 @@ public class ZenModeEditNamePreferenceControllerTest {

    private ZenModeEditNamePreferenceController mController;
    private LayoutPreference mPreference;
    private TextInputLayout mTextInputLayout;
    private EditText mEditText;
    @Mock private Consumer<String> mNameSetter;

@@ -64,12 +67,15 @@ public class ZenModeEditNamePreferenceControllerTest {

        Context context = RuntimeEnvironment.application;
        PreferenceManager preferenceManager = new PreferenceManager(context);

        // Inflation is a test in itself, because it will crash if the Theme isn't set correctly.
        PreferenceScreen preferenceScreen = preferenceManager.inflateFromResource(context,
                R.xml.modes_edit_name_icon, null);
        mPreference = preferenceScreen.findPreference("name");

        mController = new ZenModeEditNamePreferenceController(context, "name", mNameSetter);
        mController.displayPreference(preferenceScreen);
        mTextInputLayout = mPreference.findViewById(R.id.edit_input_layout);
        mEditText = mPreference.findViewById(android.R.id.edit);
        assertThat(mEditText).isNotNull();
    }
@@ -88,11 +94,24 @@ public class ZenModeEditNamePreferenceControllerTest {
    public void onEditText_callsNameSetter() {
        ZenMode mode = new TestModeBuilder().setName("A fancy name").build();
        mController.updateState(mPreference, mode);
        EditText editText = mPreference.findViewById(android.R.id.edit);

        editText.setText("An even fancier name");
        mEditText.setText("An even fancier name");

        verify(mNameSetter).accept("An even fancier name");
        verifyNoMoreInteractions(mNameSetter);
    }

    @Test
    public void onEditText_emptyText_showsError() {
        ZenMode mode = new TestModeBuilder().setName("Default name").build();
        mController.updateState(mPreference, mode);

        mEditText.setText("");

        assertThat(mTextInputLayout.getError()).isNotNull();

        mEditText.setText("this is fine");

        assertThat(mTextInputLayout.getError()).isNull();
    }
}