Skip to content

Commit

Permalink
[DatePicker] Exposed method to set a custom formatter for text input …
Browse files Browse the repository at this point in the history
…fields.

Resolves #3039

PiperOrigin-RevId: 485128314
  • Loading branch information
hunterstich committed Nov 1, 2022
1 parent d0ca6ba commit 276c117
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 15 deletions.
11 changes: 11 additions & 0 deletions lib/java/com/google/android/material/datepicker/DateSelector.java
Expand Up @@ -33,6 +33,7 @@
import androidx.annotation.StyleRes;
import androidx.core.util.Pair;
import com.google.android.material.internal.ViewUtils;
import java.text.SimpleDateFormat;
import java.util.Collection;

/**
Expand Down Expand Up @@ -105,6 +106,16 @@ public interface DateSelector<S> extends Parcelable {
@StyleRes
int getDefaultThemeResId(Context context);

/**
* Sets the {@link SimpleDateFormat} used to format the text input field hint and error.
*
* <p>When this is set to null, a default formatter will be used that properly adjusts for
* language and locale.
*
* @param format The format to be used when formatting the text input field
*/
void setTextInputFormat(@Nullable SimpleDateFormat format);

@NonNull
View onCreateTextInputView(
@NonNull LayoutInflater layoutInflater,
Expand Down
Expand Up @@ -64,6 +64,7 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
import java.util.LinkedHashSet;

/** A {@link Dialog} with a header, {@link MaterialCalendar}, and set of actions. */
Expand Down Expand Up @@ -678,6 +679,22 @@ public static Builder<Pair<Long, Long>> dateRangePicker() {
return new Builder<>(new RangeDateSelector());
}

/**
* Sets the formatter that will be used to input dates using a keyboard.
*
* <p>This affects the hint text and error suggestions of the date input field. Using this
* setter requires caution to ensure dates are formatted properly in different languages and
* locales.
*
* @param format a {@link SimpleDateFormat} used to format text input dates
*/
@NonNull
@CanIgnoreReturnValue
public Builder<S> setTextInputFormat(@Nullable SimpleDateFormat format) {
dateSelector.setTextInputFormat(format);
return this;
}

@NonNull
@CanIgnoreReturnValue
public Builder<S> setSelection(S selection) {
Expand Down
Expand Up @@ -58,6 +58,8 @@ public class RangeDateSelector implements DateSelector<Pair<Long, Long>> {
@Nullable private Long proposedTextStart = null;
@Nullable private Long proposedTextEnd = null;

@Nullable private SimpleDateFormat textInputFormat;

@Override
public void select(long selection) {
if (selectedStartItem == null) {
Expand Down Expand Up @@ -179,6 +181,11 @@ public int getDefaultTitleResId() {
return R.string.mtrl_picker_range_header_title;
}

@Override
public void setTextInputFormat(@Nullable SimpleDateFormat format) {
this.textInputFormat = format;
}

@Override
public View onCreateTextInputView(
@NonNull LayoutInflater layoutInflater,
Expand All @@ -202,7 +209,9 @@ public View onCreateTextInputView(

invalidRangeStartError = root.getResources().getString(R.string.mtrl_picker_invalid_range);

SimpleDateFormat format = UtcDates.getTextInputFormat();
boolean hasCustomFormat = textInputFormat != null;
SimpleDateFormat format =
hasCustomFormat ? textInputFormat : UtcDates.getDefaultTextInputFormat();

if (selectedStartItem != null) {
startEditText.setText(format.format(selectedStartItem));
Expand All @@ -213,7 +222,11 @@ public View onCreateTextInputView(
proposedTextEnd = selectedEndItem;
}

String formatHint = UtcDates.getTextInputHint(root.getResources(), format);
String formatHint =
hasCustomFormat
? format.toPattern()
: UtcDates.getDefaultTextInputHint(root.getResources(), format);

startTextInput.setPlaceholderText(formatHint);
endTextInput.setPlaceholderText(formatHint);

Expand Down
Expand Up @@ -48,6 +48,7 @@
public class SingleDateSelector implements DateSelector<Long> {

@Nullable private Long selectedItem;
@Nullable private SimpleDateFormat textInputFormat;

@Override
public void select(long selection) {
Expand Down Expand Up @@ -90,6 +91,11 @@ public Long getSelection() {
return selectedItem;
}

@Override
public void setTextInputFormat(@Nullable SimpleDateFormat format) {
this.textInputFormat = format;
}

@Override
public View onCreateTextInputView(
@NonNull LayoutInflater layoutInflater,
Expand All @@ -105,8 +111,14 @@ public View onCreateTextInputView(
// Using the URI variation places the '/' and '.' in more prominent positions
dateEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
}
SimpleDateFormat format = UtcDates.getTextInputFormat();
String formatHint = UtcDates.getTextInputHint(root.getResources(), format);

boolean hasCustomFormat = textInputFormat != null;
SimpleDateFormat format =
hasCustomFormat ? textInputFormat : UtcDates.getDefaultTextInputFormat();
String formatHint =
hasCustomFormat
? format.toPattern()
: UtcDates.getDefaultTextInputHint(root.getResources(), format);

dateTextInput.setPlaceholderText(formatHint);
if (selectedItem != null) {
Expand Down
8 changes: 4 additions & 4 deletions lib/java/com/google/android/material/datepicker/UtcDates.java
Expand Up @@ -147,18 +147,18 @@ private static DateFormat getFormat(int style, Locale locale) {
return format;
}

static SimpleDateFormat getTextInputFormat() {
String pattern =
static SimpleDateFormat getDefaultTextInputFormat() {
String defaultFormatPattern =
((SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()))
.toPattern()
.replaceAll("\\s+", "");
SimpleDateFormat format = new SimpleDateFormat(pattern, Locale.getDefault());
SimpleDateFormat format = new SimpleDateFormat(defaultFormatPattern, Locale.getDefault());
format.setTimeZone(UtcDates.getTimeZone());
format.setLenient(false);
return format;
}

static String getTextInputHint(Resources res, SimpleDateFormat format) {
static String getDefaultTextInputHint(Resources res, SimpleDateFormat format) {
String formatHint = format.toPattern();
String yearChar = res.getString(R.string.mtrl_picker_text_input_year_abbr);
String monthChar = res.getString(R.string.mtrl_picker_text_input_month_abbr);
Expand Down
Expand Up @@ -100,6 +100,27 @@ public void textInputFormatError() {
assertThat(endTextInput.getError()).isNotNull();
}

@Test
public void textFieldFormatPlaceholder() {
View root = getRootView();
((ViewGroup) activity.findViewById(android.R.id.content)).addView(root);

TextInputLayout startTextInput = root.findViewById(R.id.mtrl_picker_text_input_range_start);

assertThat(startTextInput.getPlaceholderText().toString()).isEqualTo("m/d/yy");
}

@Test
public void customTextFieldFormatPlaceholder() {
rangeDateSelector.setTextInputFormat(new SimpleDateFormat("MM/dd/y,ww"));
View root = getRootView();
((ViewGroup) activity.findViewById(android.R.id.content)).addView(root);

TextInputLayout startTextInput = root.findViewById(R.id.mtrl_picker_text_input_range_start);

assertThat(startTextInput.getPlaceholderText().toString()).isEqualTo("MM/dd/y,ww");
}

@Test
public void textInputRangeError() {
rangeDateSelector = new RangeDateSelector();
Expand Down
Expand Up @@ -22,30 +22,35 @@
import android.content.Context;
import android.content.res.Resources;
import androidx.appcompat.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridView;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import com.google.android.material.internal.ParcelableTestUtils;
import com.google.android.material.textfield.TextInputLayout;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.internal.DoNotInstrument;

@RunWith(RobolectricTestRunner.class)
@DoNotInstrument
public class SingleDateSelectorTest {

private SingleDateSelector singleDateSelector;
private MonthAdapter adapter;
private Context context;
private Resources res;
private AppCompatActivity activity;

@Before
public void setupMonthAdapters() {
ApplicationProvider.getApplicationContext().setTheme(R.style.Theme_MaterialComponents_Light);
AppCompatActivity activity = Robolectric.buildActivity(AppCompatActivity.class).setup().get();
activity = Robolectric.buildActivity(AppCompatActivity.class).setup().get();
context = activity.getApplicationContext();
res = context.getResources();
GridView gridView = new GridView(context);
Expand Down Expand Up @@ -131,4 +136,37 @@ public void getSelectedRanges_isEmpty() {

assertThat(singleDateSelector.getSelectedRanges().isEmpty()).isTrue();
}

@Test
public void textFieldPlaceholder_usesDefaultFormat() {
View root = getRootView();
((ViewGroup) activity.findViewById(android.R.id.content)).addView(root);

TextInputLayout textInputLayout = root.findViewById(R.id.mtrl_picker_text_input_date);

assertThat(textInputLayout.getPlaceholderText().toString()).isEqualTo("m/d/yy");
}

@Test
public void textFieldPlaceholder_usesCustomFormat() {
singleDateSelector.setTextInputFormat(new SimpleDateFormat("kk:mm:ss mm/dd/yyyy"));
View root = getRootView();
((ViewGroup) activity.findViewById(android.R.id.content)).addView(root);

TextInputLayout textInputLayout = root.findViewById(R.id.mtrl_picker_text_input_date);

assertThat(textInputLayout.getPlaceholderText().toString()).isEqualTo("kk:mm:ss mm/dd/yyyy");
}

private View getRootView() {
return singleDateSelector.onCreateTextInputView(
LayoutInflater.from(context),
null,
null,
new CalendarConstraints.Builder().build(),
new OnSelectionChangedListener<Long>() {
@Override
public void onSelectionChanged(@NonNull Long selection) {}
});
}
}
Expand Up @@ -45,23 +45,23 @@ public void setup() {
@Test
public void textInputHintWith1CharYear() {
SimpleDateFormat sdf = new SimpleDateFormat("M/d/y");
String hint = UtcDates.getTextInputHint(context.getResources(), sdf);
String hint = UtcDates.getDefaultTextInputHint(context.getResources(), sdf);

assertEquals("m/d/yyyy", hint);
}

@Test
public void textInputHintWith2CharYear() {
SimpleDateFormat sdf = new SimpleDateFormat("M/d/yy");
String hint = UtcDates.getTextInputHint(context.getResources(), sdf);
String hint = UtcDates.getDefaultTextInputHint(context.getResources(), sdf);

assertEquals("m/d/yy", hint);
}

@Test
public void textInputHintWith4CharYear() {
SimpleDateFormat sdf = new SimpleDateFormat("M/d/yyyy");
String hint = UtcDates.getTextInputHint(context.getResources(), sdf);
String hint = UtcDates.getDefaultTextInputHint(context.getResources(), sdf);

assertEquals("m/d/yyyy", hint);
}
Expand All @@ -70,7 +70,7 @@ public void textInputHintWith4CharYear() {
@Config(qualifiers = "fr-rFR")
public void textInputHintWith1CharYearLocalized() {
SimpleDateFormat sdf = new SimpleDateFormat("M/d/y");
String hint = UtcDates.getTextInputHint(context.getResources(), sdf);
String hint = UtcDates.getDefaultTextInputHint(context.getResources(), sdf);

assertEquals("m/j/aaaa", hint);
}
Expand Down

0 comments on commit 276c117

Please sign in to comment.