Skip to content

Commit 7bc5689

Browse files
paulfthomasafohrman
authored andcommitted
[MaterialDatePicker] Add method to specify a content description to decorated views
PiperOrigin-RevId: 474622363
1 parent 09f1ee5 commit 7bc5689

File tree

7 files changed

+175
-11
lines changed

7 files changed

+175
-11
lines changed

catalog/java/io/material/catalog/datepicker/BackgroundHighlightDecorator.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,24 @@ public ColorStateList getBackgroundColor(
5757
: null;
5858
}
5959

60+
@Nullable
61+
@Override
62+
public CharSequence getContentDescription(
63+
@NonNull Context context,
64+
int year,
65+
int month,
66+
int day,
67+
boolean valid,
68+
boolean selected,
69+
@Nullable CharSequence originalContentDescription) {
70+
if (!valid || !shouldShowHighlight(year, month, day)) {
71+
return originalContentDescription;
72+
}
73+
return String.format(
74+
context.getString(R.string.cat_picker_day_view_decorator_highlights_content_description),
75+
originalContentDescription);
76+
}
77+
6078
private boolean shouldShowHighlight(int year, int month, int day) {
6179
for (Calendar calendar : highlightDays) {
6280
if (calendar.get(Calendar.YEAR) == year

catalog/java/io/material/catalog/datepicker/CircleIndicatorDecorator.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import android.os.Parcelable;
2929
import androidx.annotation.ColorInt;
3030
import androidx.annotation.NonNull;
31+
import androidx.annotation.Nullable;
3132
import com.google.android.material.color.MaterialColors;
3233
import com.google.android.material.datepicker.DayViewDecorator;
3334
import java.util.ArrayList;
@@ -61,6 +62,24 @@ public Drawable getCompoundDrawableBottom(
6162
return selectIndicatorDrawable(year, month, day, valid, selected);
6263
}
6364

65+
@Nullable
66+
@Override
67+
public CharSequence getContentDescription(
68+
@NonNull Context context,
69+
int year,
70+
int month,
71+
int day,
72+
boolean valid,
73+
boolean selected,
74+
@Nullable CharSequence originalContentDescription) {
75+
if (!valid || !shouldShowIndicator(year, month, day)) {
76+
return originalContentDescription;
77+
}
78+
return String.format(
79+
context.getString(R.string.cat_picker_day_view_decorator_dots_content_description),
80+
originalContentDescription);
81+
}
82+
6483
private Drawable selectIndicatorDrawable(
6584
int year, int month, int day, boolean valid, boolean selected) {
6685
if (!valid || !shouldShowIndicator(year, month, day)) {

catalog/java/io/material/catalog/datepicker/res/values/strings.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
<string name="cat_picker_day_view_decorator_none">None</string>
119119
<!-- Indicates that the picker will use a dots day view decorator [CHAR LIMIT=50] -->
120120
<string name="cat_picker_day_view_decorator_dots">Dots</string>
121-
<!-- Indicates that the picker will use a dots day view decorator [CHAR LIMIT=50] -->
121+
<!-- Indicates that the picker will use a highlights day view decorator [CHAR LIMIT=50] -->
122122
<string name="cat_picker_day_view_decorator_highlights">Highlights</string>
123123

124124
<!-- Grouping label for a set of radio buttons that choose the positive button [CHAR LIMIT=50] -->
@@ -139,4 +139,9 @@
139139
<string name="cat_picker_positive_button_text">DONE</string>
140140
<!-- The custom negative button text -->
141141
<string name="cat_picker_negative_button_text">Negative</string>
142+
143+
<!-- The custom dots day view decorator's content description [CHAR LIMIT=120] -->
144+
<string name="cat_picker_day_view_decorator_dots_content_description">%s Dotted</string>
145+
<!-- The custom highlights day view decorator's content description [CHAR LIMIT=120] -->
146+
<string name="cat_picker_day_view_decorator_highlights_content_description">%s Highlighted</string>
142147
</resources>

lib/java/com/google/android/material/datepicker/DateStrings.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,19 @@ static String getYearMonthDayOfWeekDay(long timeInMillis, Locale locale) {
100100
return UtcDates.getFullFormat(locale).format(new Date(timeInMillis));
101101
}
102102

103+
/**
104+
* Does not show year if date is within current year.
105+
*
106+
* @param timeInMillis milliseconds since UTC epoch.
107+
* @return Formatted date string.
108+
*/
109+
static String getOptionalYearMonthDayOfWeekDay(long timeInMillis) {
110+
if (isDateWithinCurrentYear(timeInMillis)) {
111+
return getMonthDayOfWeekDay(timeInMillis);
112+
}
113+
return getYearMonthDayOfWeekDay(timeInMillis);
114+
}
115+
103116
static String getDateString(long timeInMillis) {
104117
return getDateString(timeInMillis, null);
105118
}
@@ -116,19 +129,22 @@ static String getDateString(long timeInMillis) {
116129
* @return Formatted date string.
117130
*/
118131
static String getDateString(long timeInMillis, @Nullable SimpleDateFormat userDefinedDateFormat) {
119-
Calendar currentCalendar = UtcDates.getTodayCalendar();
120-
Calendar calendarDate = UtcDates.getUtcCalendar();
121-
calendarDate.setTimeInMillis(timeInMillis);
122-
123132
if (userDefinedDateFormat != null) {
124133
Date date = new Date(timeInMillis);
125134
return userDefinedDateFormat.format(date);
126-
} else if (currentCalendar.get(Calendar.YEAR) == calendarDate.get(Calendar.YEAR)) {
135+
} else if (isDateWithinCurrentYear(timeInMillis)) {
127136
return getMonthDay(timeInMillis);
128137
}
129138
return getYearMonthDay(timeInMillis);
130139
}
131140

141+
private static boolean isDateWithinCurrentYear(long timeInMillis) {
142+
Calendar currentCalendar = UtcDates.getTodayCalendar();
143+
Calendar calendarDate = UtcDates.getUtcCalendar();
144+
calendarDate.setTimeInMillis(timeInMillis);
145+
return currentCalendar.get(Calendar.YEAR) == calendarDate.get(Calendar.YEAR);
146+
}
147+
132148
static Pair<String, String> getDateRangeString(@Nullable Long start, @Nullable Long end) {
133149
return getDateRangeString(start, end, null);
134150
}

lib/java/com/google/android/material/datepicker/DayViewDecorator.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,32 @@ public ColorStateList getBackgroundColor(
146146
@NonNull Context context, int year, int month, int day, boolean valid, boolean selected) {
147147
return null;
148148
}
149+
150+
/**
151+
* Override this method to return the day view's content description.
152+
*
153+
* @param context The context of the day view
154+
* @param year The year number corresponding to the day view (see {@link java.util.Calendar.YEAR})
155+
* @param month The month number (0-11) corresponding to the day view (see {@link
156+
* java.util.Calendar.MONTH})
157+
* @param day The day of month number corresponding to the day view (see {@link
158+
* java.util.Calendar.DAY_OF_MONTH})
159+
* @param valid Boolean for whether the day view is in a valid state (if not valid, the day view
160+
* will likely look and behave disabled)
161+
* @param selected Boolean for whether the day view is in a selected state (if selected, the day
162+
* view will likely have a filled color background)
163+
* @param originalContentDescription The original day view's content description
164+
* @return The content description
165+
*/
166+
@Nullable
167+
public CharSequence getContentDescription(
168+
@NonNull Context context,
169+
int year,
170+
int month,
171+
int day,
172+
boolean valid,
173+
boolean selected,
174+
@Nullable CharSequence originalContentDescription) {
175+
return originalContentDescription;
176+
}
149177
}

lib/java/com/google/android/material/datepicker/MonthAdapter.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,7 @@ public TextView getView(int position, @Nullable View convertView, @NonNull ViewG
138138
Locale locale = dayTextView.getResources().getConfiguration().locale;
139139
dayTextView.setText(String.format(locale, "%d", dayNumber));
140140
long dayInMillis = month.getDay(dayNumber);
141-
if (month.year == Month.current().year) {
142-
dayTextView.setContentDescription(DateStrings.getMonthDayOfWeekDay(dayInMillis));
143-
} else {
144-
dayTextView.setContentDescription(DateStrings.getYearMonthDayOfWeekDay(dayInMillis));
145-
}
141+
dayTextView.setContentDescription(DateStrings.getOptionalYearMonthDayOfWeekDay(dayInMillis));
146142
dayTextView.setVisibility(View.VISIBLE);
147143
dayTextView.setEnabled(true);
148144
}
@@ -208,6 +204,7 @@ private void updateSelectedState(@Nullable TextView dayTextView, long date, int
208204

209205
if (dayViewDecorator != null && dayNumber != NO_DAY_NUMBER) {
210206
Context context = dayTextView.getContext();
207+
long dayInMillis = month.getDay(dayNumber);
211208
int year = month.year;
212209
int month = this.month.month;
213210

@@ -227,6 +224,17 @@ private void updateSelectedState(@Nullable TextView dayTextView, long date, int
227224
dayViewDecorator.getCompoundDrawableBottom(
228225
context, year, month, dayNumber, valid, selected);
229226
dayTextView.setCompoundDrawables(drawableLeft, drawableTop, drawableRight, drawableBottom);
227+
228+
CharSequence decoratorContentDescription =
229+
dayViewDecorator.getContentDescription(
230+
context,
231+
year,
232+
month,
233+
dayNumber,
234+
valid,
235+
selected,
236+
DateStrings.getOptionalYearMonthDayOfWeekDay(dayInMillis));
237+
dayTextView.setContentDescription(decoratorContentDescription);
230238
} else {
231239
style.styleItem(dayTextView);
232240
}

lib/javatests/com/google/android/material/datepicker/MonthAdapterTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@
2121
import static org.junit.Assert.assertEquals;
2222
import static org.junit.Assert.assertNotNull;
2323
import static org.junit.Assert.assertNull;
24+
import static org.junit.Assert.assertTrue;
2425

26+
import android.content.Context;
27+
import android.os.Parcel;
28+
import androidx.annotation.NonNull;
29+
import androidx.annotation.Nullable;
2530
import androidx.test.core.app.ApplicationProvider;
2631
import java.util.Arrays;
2732
import java.util.Calendar;
@@ -261,6 +266,71 @@ public void ilDaysOfPositions() {
261266
assertDaysOfPositions(localizedDaysOfPositionsInFebruary2019);
262267
}
263268

269+
@Test
270+
public void dayViewDecorator_withIndicator_hasUpdatedContentDescription() {
271+
DayViewDecorator decorator = getDecoratedMonthAdapter().dayViewDecorator;
272+
273+
CharSequence decoratorContentDescription =
274+
decorator.getContentDescription(
275+
ApplicationProvider.getApplicationContext(),
276+
2018,
277+
Calendar.JANUARY,
278+
17,
279+
true,
280+
false,
281+
"Original content description");
282+
assertTrue("Original content description Test".contentEquals(decoratorContentDescription));
283+
}
284+
285+
@Test
286+
public void dayViewDecorator_withoutIndicator_hasOriginalContentDescription() {
287+
DayViewDecorator decorator = getDecoratedMonthAdapter().dayViewDecorator;
288+
289+
CharSequence decoratorContentDescription =
290+
decorator.getContentDescription(
291+
ApplicationProvider.getApplicationContext(),
292+
2018,
293+
Calendar.JANUARY,
294+
16,
295+
true,
296+
false,
297+
"Original content description");
298+
assertTrue("Original content description".contentEquals(decoratorContentDescription));
299+
}
300+
301+
private MonthAdapter getDecoratedMonthAdapter() {
302+
return new MonthAdapter(
303+
Month.create(2018, Calendar.JANUARY),
304+
new SingleDateSelector(),
305+
new CalendarConstraints.Builder().build(),
306+
new DayViewDecorator() {
307+
@Nullable
308+
@Override
309+
public CharSequence getContentDescription(
310+
@NonNull Context context,
311+
int year,
312+
int month,
313+
int day,
314+
boolean valid,
315+
boolean selected,
316+
@Nullable CharSequence originalContentDescription) {
317+
if (year == 2018 && month == Calendar.JANUARY && day == 17) {
318+
return originalContentDescription + " Test";
319+
}
320+
return super.getContentDescription(
321+
context, year, month, day, valid, selected, originalContentDescription);
322+
}
323+
324+
@Override
325+
public int describeContents() {
326+
return 0;
327+
}
328+
329+
@Override
330+
public void writeToParcel(@NonNull Parcel dest, int flags) {}
331+
});
332+
}
333+
264334
private void assertDaysOfPositions(Map<Integer, Integer> localizedDaysOfPositionsInFebruary2019) {
265335
for (int day : localizedDaysOfPositionsInFebruary2019.keySet()) {
266336
Calendar testCalendar = UtcDates.getUtcCalendar();

0 commit comments

Comments
 (0)