Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.widget;
     18 
     19 import android.content.Context;
     20 import android.os.LocaleList;
     21 import android.text.Editable;
     22 import android.text.InputFilter;
     23 import android.text.TextWatcher;
     24 import android.util.AttributeSet;
     25 import android.util.MathUtils;
     26 import android.view.View;
     27 
     28 import com.android.internal.R;
     29 
     30 /**
     31  * View to show text input based time picker with hour and minute fields and an optional AM/PM
     32  * spinner.
     33  *
     34  * @hide
     35  */
     36 public class TextInputTimePickerView extends RelativeLayout {
     37     public static final int HOURS = 0;
     38     public static final int MINUTES = 1;
     39     public static final int AMPM = 2;
     40 
     41     private static final int AM = 0;
     42     private static final int PM = 1;
     43 
     44     private final EditText mHourEditText;
     45     private final EditText mMinuteEditText;
     46     private final TextView mInputSeparatorView;
     47     private final Spinner mAmPmSpinner;
     48     private final TextView mErrorLabel;
     49     private final TextView mHourLabel;
     50     private final TextView mMinuteLabel;
     51 
     52     private boolean mIs24Hour;
     53     private boolean mHourFormatStartsAtZero;
     54     private OnValueTypedListener mListener;
     55 
     56     private boolean mErrorShowing;
     57 
     58     interface OnValueTypedListener {
     59         void onValueChanged(int inputType, int newValue);
     60     }
     61 
     62     public TextInputTimePickerView(Context context) {
     63         this(context, null);
     64     }
     65 
     66     public TextInputTimePickerView(Context context, AttributeSet attrs) {
     67         this(context, attrs, 0);
     68     }
     69 
     70     public TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle) {
     71         this(context, attrs, defStyle, 0);
     72     }
     73 
     74     public TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle,
     75             int defStyleRes) {
     76         super(context, attrs, defStyle, defStyleRes);
     77 
     78         inflate(context, R.layout.time_picker_text_input_material, this);
     79 
     80         mHourEditText = findViewById(R.id.input_hour);
     81         mMinuteEditText = findViewById(R.id.input_minute);
     82         mInputSeparatorView = findViewById(R.id.input_separator);
     83         mErrorLabel = findViewById(R.id.label_error);
     84         mHourLabel = findViewById(R.id.label_hour);
     85         mMinuteLabel = findViewById(R.id.label_minute);
     86 
     87         mHourEditText.addTextChangedListener(new TextWatcher() {
     88             @Override
     89             public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
     90 
     91             @Override
     92             public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
     93 
     94             @Override
     95             public void afterTextChanged(Editable editable) {
     96                 parseAndSetHourInternal(editable.toString());
     97             }
     98         });
     99 
    100         mMinuteEditText.addTextChangedListener(new TextWatcher() {
    101             @Override
    102             public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
    103 
    104             @Override
    105             public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
    106 
    107             @Override
    108             public void afterTextChanged(Editable editable) {
    109                 parseAndSetMinuteInternal(editable.toString());
    110             }
    111         });
    112 
    113         mAmPmSpinner = findViewById(R.id.am_pm_spinner);
    114         final String[] amPmStrings = TimePicker.getAmPmStrings(context);
    115         ArrayAdapter<CharSequence> adapter =
    116                 new ArrayAdapter<CharSequence>(context, R.layout.simple_spinner_dropdown_item);
    117         adapter.add(TimePickerClockDelegate.obtainVerbatim(amPmStrings[0]));
    118         adapter.add(TimePickerClockDelegate.obtainVerbatim(amPmStrings[1]));
    119         mAmPmSpinner.setAdapter(adapter);
    120         mAmPmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    121             @Override
    122             public void onItemSelected(AdapterView<?> adapterView, View view, int position,
    123                     long id) {
    124                 if (position == 0) {
    125                     mListener.onValueChanged(AMPM, AM);
    126                 } else {
    127                     mListener.onValueChanged(AMPM, PM);
    128                 }
    129             }
    130 
    131             @Override
    132             public void onNothingSelected(AdapterView<?> adapterView) {}
    133         });
    134     }
    135 
    136     void setListener(OnValueTypedListener listener) {
    137         mListener = listener;
    138     }
    139 
    140     void setHourFormat(int maxCharLength) {
    141         mHourEditText.setFilters(new InputFilter[] {
    142                 new InputFilter.LengthFilter(maxCharLength)});
    143         mMinuteEditText.setFilters(new InputFilter[] {
    144                 new InputFilter.LengthFilter(maxCharLength)});
    145         final LocaleList locales = mContext.getResources().getConfiguration().getLocales();
    146         mHourEditText.setImeHintLocales(locales);
    147         mMinuteEditText.setImeHintLocales(locales);
    148     }
    149 
    150     boolean validateInput() {
    151         final boolean inputValid = parseAndSetHourInternal(mHourEditText.getText().toString())
    152                 && parseAndSetMinuteInternal(mMinuteEditText.getText().toString());
    153         setError(!inputValid);
    154         return inputValid;
    155     }
    156 
    157     void updateSeparator(String separatorText) {
    158         mInputSeparatorView.setText(separatorText);
    159     }
    160 
    161     private void setError(boolean enabled) {
    162         mErrorShowing = enabled;
    163 
    164         mErrorLabel.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
    165         mHourLabel.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE);
    166         mMinuteLabel.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE);
    167     }
    168 
    169     /**
    170      * Computes the display value and updates the text of the view.
    171      * <p>
    172      * This method should be called whenever the current value or display
    173      * properties (leading zeroes, max digits) change.
    174      */
    175     void updateTextInputValues(int localizedHour, int minute, int amOrPm, boolean is24Hour,
    176             boolean hourFormatStartsAtZero) {
    177         final String hourFormat = "%d";
    178         final String minuteFormat = "%02d";
    179 
    180         mIs24Hour = is24Hour;
    181         mHourFormatStartsAtZero = hourFormatStartsAtZero;
    182 
    183         mAmPmSpinner.setVisibility(is24Hour ? View.INVISIBLE : View.VISIBLE);
    184 
    185         if (amOrPm == AM) {
    186             mAmPmSpinner.setSelection(0);
    187         } else {
    188             mAmPmSpinner.setSelection(1);
    189         }
    190 
    191         mHourEditText.setText(String.format(hourFormat, localizedHour));
    192         mMinuteEditText.setText(String.format(minuteFormat, minute));
    193 
    194         if (mErrorShowing) {
    195             validateInput();
    196         }
    197     }
    198 
    199     private boolean parseAndSetHourInternal(String input) {
    200         try {
    201             final int hour = Integer.parseInt(input);
    202             if (!isValidLocalizedHour(hour)) {
    203                 final int minHour = mHourFormatStartsAtZero ? 0 : 1;
    204                 final int maxHour = mIs24Hour ? 23 : 11 + minHour;
    205                 mListener.onValueChanged(HOURS, getHourOfDayFromLocalizedHour(
    206                         MathUtils.constrain(hour, minHour, maxHour)));
    207                 return false;
    208             }
    209             mListener.onValueChanged(HOURS, getHourOfDayFromLocalizedHour(hour));
    210             return true;
    211         } catch (NumberFormatException e) {
    212             // Do nothing since we cannot parse the input.
    213             return false;
    214         }
    215     }
    216 
    217     private boolean parseAndSetMinuteInternal(String input) {
    218         try {
    219             final int minutes = Integer.parseInt(input);
    220             if (minutes < 0 || minutes > 59) {
    221                 mListener.onValueChanged(MINUTES, MathUtils.constrain(minutes, 0, 59));
    222                 return false;
    223             }
    224             mListener.onValueChanged(MINUTES, minutes);
    225             return true;
    226         } catch (NumberFormatException e) {
    227             // Do nothing since we cannot parse the input.
    228             return false;
    229         }
    230     }
    231 
    232     private boolean isValidLocalizedHour(int localizedHour) {
    233         final int minHour = mHourFormatStartsAtZero ? 0 : 1;
    234         final int maxHour = (mIs24Hour ? 23 : 11) + minHour;
    235         return localizedHour >= minHour && localizedHour <= maxHour;
    236     }
    237 
    238     private int getHourOfDayFromLocalizedHour(int localizedHour) {
    239         int hourOfDay = localizedHour;
    240         if (mIs24Hour) {
    241             if (!mHourFormatStartsAtZero && localizedHour == 24) {
    242                 hourOfDay = 0;
    243             }
    244         } else {
    245             if (!mHourFormatStartsAtZero && localizedHour == 12) {
    246                 hourOfDay = 0;
    247             }
    248             if (mAmPmSpinner.getSelectedItemPosition() == 1) {
    249                 hourOfDay += 12;
    250             }
    251         }
    252         return hourOfDay;
    253     }
    254 }
    255