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