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

Commit ab14d825 authored by Aurimas Liutikas's avatar Aurimas Liutikas
Browse files

Adding text input mode to TimePicker.

Test: Tests will have a follow up CL.
Bug:30044800

Change-Id: I71a0fba97026da76251768016edf1b159617abb9
parent 40eb0ba4
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -50543,6 +50543,7 @@ package android.widget {
    method public void setIs24HourView(java.lang.Boolean);
    method public void setMinute(int);
    method public void setOnTimeChangedListener(android.widget.TimePicker.OnTimeChangedListener);
    method public boolean validateInput();
  }
  public static abstract interface TimePicker.OnTimeChangedListener {
+1 −0
Original line number Diff line number Diff line
@@ -54314,6 +54314,7 @@ package android.widget {
    method public void setIs24HourView(java.lang.Boolean);
    method public void setMinute(int);
    method public void setOnTimeChangedListener(android.widget.TimePicker.OnTimeChangedListener);
    method public boolean validateInput();
  }
  public static abstract interface TimePicker.OnTimeChangedListener {
+1 −0
Original line number Diff line number Diff line
@@ -50868,6 +50868,7 @@ package android.widget {
    method public void setIs24HourView(java.lang.Boolean);
    method public void setMinute(int);
    method public void setOnTimeChangedListener(android.widget.TimePicker.OnTimeChangedListener);
    method public boolean validateInput();
    field public static final int MODE_CLOCK = 2; // 0x2
    field public static final int MODE_SPINNER = 1; // 0x1
  }
+17 −6
Original line number Diff line number Diff line
@@ -145,14 +145,25 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener,
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        switch (which) {
            case BUTTON_POSITIVE:
    public void show() {
        super.show();
        getButton(BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mTimePicker.validateInput()) {
                    if (mTimeSetListener != null) {
                        mTimeSetListener.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(),
                                mTimePicker.getCurrentMinute());
                    }
                break;
                    dismiss();
                }
            }
        });
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        switch (which) {
            case BUTTON_NEGATIVE:
                cancel();
                break;
+249 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 android.widget;

import android.content.Context;
import android.text.Editable;
import android.text.InputFilter;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.view.View;

import com.android.internal.R;

/**
 * View to show text input based time picker with hour and minute fields and an optional AM/PM
 * spinner.
 *
 * @hide
 */
public class TextInputTimePickerView extends RelativeLayout {
    public static final int HOURS = 0;
    public static final int MINUTES = 1;
    public static final int AMPM = 2;

    private static final int AM = 0;
    private static final int PM = 1;

    private final EditText mHourEditText;
    private final EditText mMinuteEditText;
    private final TextView mInputSeparatorView;
    private final Spinner mAmPmSpinner;
    private final TextView mErrorLabel;
    private final TextView mHourLabel;
    private final TextView mMinuteLabel;

    private boolean mIs24Hour;
    private boolean mHourFormatStartsAtZero;
    private OnValueTypedListener mListener;

    private boolean mErrorShowing;

    interface OnValueTypedListener {
        void onValueChanged(int inputType, int newValue);
    }

    public TextInputTimePickerView(Context context) {
        this(context, null);
    }

    public TextInputTimePickerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle) {
        this(context, attrs, defStyle, 0);
    }

    public TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle,
            int defStyleRes) {
        super(context, attrs, defStyle, defStyleRes);

        inflate(context, R.layout.time_picker_text_input_material, this);

        mHourEditText = (EditText) findViewById(R.id.input_hour);
        mMinuteEditText = (EditText) findViewById(R.id.input_minute);
        mInputSeparatorView = (TextView) findViewById(R.id.input_separator);
        mErrorLabel = (TextView) findViewById(R.id.label_error);
        mHourLabel = (TextView) findViewById(R.id.label_hour);
        mMinuteLabel = (TextView) findViewById(R.id.label_minute);

        mHourEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}

            @Override
            public void afterTextChanged(Editable editable) {
                parseAndSetHourInternal(editable.toString());
            }
        });

        mMinuteEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}

            @Override
            public void afterTextChanged(Editable editable) {
                parseAndSetMinuteInternal(editable.toString());
            }
        });

        mAmPmSpinner = (Spinner) findViewById(R.id.am_pm_spinner);
        final String[] amPmStrings = TimePicker.getAmPmStrings(context);
        ArrayAdapter<CharSequence> adapter =
                new ArrayAdapter<CharSequence>(context, R.layout.simple_spinner_dropdown_item);
        adapter.add(TimePickerClockDelegate.obtainVerbatim(amPmStrings[0]));
        adapter.add(TimePickerClockDelegate.obtainVerbatim(amPmStrings[1]));
        mAmPmSpinner.setAdapter(adapter);
        mAmPmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int position,
                    long id) {
                if (position == 0) {
                    mListener.onValueChanged(AMPM, AM);
                } else {
                    mListener.onValueChanged(AMPM, PM);
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {}
        });
    }

    void setListener(OnValueTypedListener listener) {
        mListener = listener;
    }

    void setHourFormat(int maxCharLength) {
        mHourEditText.setFilters(new InputFilter[] {
                new InputFilter.LengthFilter(maxCharLength)});
        mMinuteEditText.setFilters(new InputFilter[] {
                new InputFilter.LengthFilter(maxCharLength)});
    }

    boolean validateInput() {
        final boolean inputValid = parseAndSetHourInternal(mHourEditText.getText().toString())
                && parseAndSetMinuteInternal(mMinuteEditText.getText().toString());
        setError(!inputValid);
        return inputValid;
    }

    void updateSeparator(String separatorText) {
        mInputSeparatorView.setText(separatorText);
    }

    private void setError(boolean enabled) {
        mErrorShowing = enabled;

        mErrorLabel.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
        mHourLabel.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE);
        mMinuteLabel.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE);
    }

    /**
     * Computes the display value and updates the text of the view.
     * <p>
     * This method should be called whenever the current value or display
     * properties (leading zeroes, max digits) change.
     */
    void updateTextInputValues(int localizedHour, int minute, int amOrPm, boolean is24Hour,
            boolean hourFormatStartsAtZero) {
        final String format = "%d";

        mIs24Hour = is24Hour;
        mHourFormatStartsAtZero = hourFormatStartsAtZero;

        mAmPmSpinner.setVisibility(is24Hour ? View.INVISIBLE : View.VISIBLE);

        mHourEditText.setText(String.format(format, localizedHour));
        mMinuteEditText.setText(String.format(format, minute));

        if (amOrPm == AM) {
            mAmPmSpinner.setSelection(0);
        } else {
            mAmPmSpinner.setSelection(1);
        }

        if (mErrorShowing) {
            validateInput();
        }
    }

    private boolean parseAndSetHourInternal(String input) {
        try {
            final int hour = Integer.parseInt(input);
            if (!isValidLocalizedHour(hour)) {
                final int minHour = mHourFormatStartsAtZero ? 0 : 1;
                final int maxHour = mIs24Hour ? 23 : 11 + minHour;
                mListener.onValueChanged(HOURS, getHourOfDayFromLocalizedHour(
                        MathUtils.constrain(hour, minHour, maxHour)));
                return false;
            }
            mListener.onValueChanged(HOURS, getHourOfDayFromLocalizedHour(hour));
            return true;
        } catch (NumberFormatException e) {
            // Do nothing since we cannot parse the input.
            return false;
        }
    }

    private boolean parseAndSetMinuteInternal(String input) {
        try {
            final int minutes = Integer.parseInt(input);
            if (minutes < 0 || minutes > 59) {
                mListener.onValueChanged(MINUTES, MathUtils.constrain(minutes, 0, 59));
                return false;
            }
            mListener.onValueChanged(MINUTES, minutes);
            return true;
        } catch (NumberFormatException e) {
            // Do nothing since we cannot parse the input.
            return false;
        }
    }

    private boolean isValidLocalizedHour(int localizedHour) {
        final int minHour = mHourFormatStartsAtZero ? 0 : 1;
        final int maxHour = (mIs24Hour ? 23 : 11) + minHour;
        return localizedHour >= minHour && localizedHour <= maxHour;
    }

    private int getHourOfDayFromLocalizedHour(int localizedHour) {
        int hourOfDay = localizedHour;
        if (mIs24Hour) {
            if (!mHourFormatStartsAtZero && localizedHour == 24) {
                hourOfDay = 0;
            }
        } else {
            if (!mHourFormatStartsAtZero && localizedHour == 12) {
                hourOfDay = 0;
            }
            if (mAmPmSpinner.getSelectedItemPosition() == 1) {
                hourOfDay += 12;
            }
        }
        return hourOfDay;
    }
}
Loading