Skip to content

Commit

Permalink
Use CalendarBounds to invalidate out of bounds dates
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 257249906
  • Loading branch information
ldjcmu authored and ikim24 committed Jul 10, 2019
1 parent 31d0e9a commit fb44b90
Show file tree
Hide file tree
Showing 22 changed files with 293 additions and 143 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public View onCreateDemoView(
dialogLaunchersLayout,
R.string.cat_picker_date_calendar_fullscreen,
MaterialDatePicker.Builder.datePicker().setTheme(fullscreenTheme));

setupDialogFragment(
dialogLaunchersLayout,
R.string.cat_picker_date_range_calendar,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,28 @@
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public final class CalendarBounds implements Parcelable {
public final class CalendarConstraints implements Parcelable {

private final Month start;
private final Month end;
private final Month current;
private final DateValidator validator;

private final int yearSpan;
private final int monthSpan;

private CalendarBounds(Month start, Month end, Month current) {
/** Used to determine whether {@link MaterialCalendar} days are enabled. */
public interface DateValidator extends Parcelable {

/** Returns true if the provided {@code date} is enabled. */
boolean isValid(long date);
}

private CalendarConstraints(Month start, Month end, Month current, DateValidator validator) {
this.start = start;
this.end = end;
this.current = current;
this.validator = validator;
if (start.compareTo(current) > 0) {
throw new IllegalArgumentException("start Month cannot be after current Month");
}
Expand All @@ -51,23 +60,37 @@ private CalendarBounds(Month start, Month end, Month current) {
}

/**
* Creates a CalendarBounds instance which opens onto {@code current} and is bounded between
* Creates a CalendarConstraints instance that opens on today if it is within the bounds or {@code
* start} if today is not within the bounds.
*/
public static CalendarConstraints create(Month start, Month end) {
Month today = Month.today();
if (end.compareTo(today) >= 0 && today.compareTo(start) >= 0) {
return create(start, end, Month.today());
}
return create(start, end, start);
}

/**
* Creates a CalendarConstraints instance which opens onto {@code current} and is bounded between
* {@code start} and {@code end}.
*/
public static CalendarBounds create(Month start, Month end, Month current) {
return new CalendarBounds(start, end, current);
public static CalendarConstraints create(Month start, Month end, Month current) {
return create(start, end, current, new DateValidatorPointForward(0));
}

/**
* Creates a CalendarBounds instance that opens on today if it is within the bounds or {@code
* start} if today is not within the bounds.
* Creates a CalendarConstraints instance which opens onto {@code current}, is bounded between
* {@code start} and {@code end}, and disables dates for which {@link DateValidator#isValid(long)}
* is false.
*/
public static CalendarBounds create(Month start, Month end) {
Month today = Month.today();
if (end.compareTo(today) >= 0 && today.compareTo(start) >= 0) {
return new CalendarBounds(start, end, Month.today());
}
return new CalendarBounds(start, end, start);
public static CalendarConstraints create(
Month start, Month end, Month current, DateValidator validator) {
return new CalendarConstraints(start, end, current, validator);
}

public DateValidator getDateValidator() {
return validator;
}

/** Returns the earliest {@link Month} allowed by this set of bounds. */
Expand Down Expand Up @@ -106,10 +129,10 @@ public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CalendarBounds)) {
if (!(o instanceof CalendarConstraints)) {
return false;
}
CalendarBounds that = (CalendarBounds) o;
CalendarConstraints that = (CalendarConstraints) o;
return start.equals(that.start) && end.equals(that.end) && current.equals(that.current);
}

Expand All @@ -122,19 +145,20 @@ public int hashCode() {
/* Parcelable interface */

/** {@link Parcelable.Creator} */
public static final Parcelable.Creator<CalendarBounds> CREATOR =
new Parcelable.Creator<CalendarBounds>() {
public static final Parcelable.Creator<CalendarConstraints> CREATOR =
new Parcelable.Creator<CalendarConstraints>() {
@Override
public CalendarBounds createFromParcel(Parcel source) {
public CalendarConstraints createFromParcel(Parcel source) {
Month start = source.readParcelable(Month.class.getClassLoader());
Month end = source.readParcelable(Month.class.getClassLoader());
Month current = source.readParcelable(Month.class.getClassLoader());
return CalendarBounds.create(start, end, current);
DateValidator validator = source.readParcelable(DateValidator.class.getClassLoader());
return CalendarConstraints.create(start, end, current, validator);
}

@Override
public CalendarBounds[] newArray(int size) {
return new CalendarBounds[size];
public CalendarConstraints[] newArray(int size) {
return new CalendarConstraints[size];
}
};

Expand All @@ -148,5 +172,6 @@ public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(start, /* parcelableFlags= */ 0);
dest.writeParcelable(end, /* parcelableFlags= */ 0);
dest.writeParcelable(current, /* parcelableFlags= */ 0);
dest.writeParcelable(validator, /* parcelableFlags = */ 0);
}
}
13 changes: 6 additions & 7 deletions lib/java/com/google/android/material/picker/CalendarStyle.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,15 @@ final class CalendarStyle {
*/
final CalendarItemStyle todayYear;

final CalendarItemStyle invalidDay;

/**
* A {@link Paint} for styling days between selected days with {@link
* R.styleable#MaterialCalendar_rangeFillColor}.
*/
final Paint rangeFill;

private final Context context;

boolean refreshStyles(Context context) {
return !this.context.equals(context);
}

CalendarStyle(Context context) {
this.context = context;
int calendarStyle =
MaterialAttributes.resolveOrThrow(
context, R.attr.materialCalendarStyle, MaterialCalendar.class.getCanonicalName());
Expand All @@ -85,6 +80,10 @@ boolean refreshStyles(Context context) {
day =
CalendarItemStyle.create(
context, calendarAttributes.getResourceId(R.styleable.MaterialCalendar_dayStyle, 0));
invalidDay =
CalendarItemStyle.create(
context,
calendarAttributes.getResourceId(R.styleable.MaterialCalendar_dayInvalidStyle, 0));
selectedDay =
CalendarItemStyle.create(
context,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2019 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 com.google.android.material.picker;

import android.os.Parcel;
import android.os.Parcelable;
import com.google.android.material.picker.CalendarConstraints.DateValidator;
import java.util.Calendar;

/**
* A {@link CalendarConstraints.DateValidator} that only allows dates from a given point onward to
* be clicked.
*/
public class DateValidatorPointForward implements DateValidator {

private final long point;

public DateValidatorPointForward() {
point = Calendar.getInstance().getTimeInMillis();
}

public DateValidatorPointForward(long point) {
this.point = point;
}

public static final Parcelable.Creator<DateValidatorPointForward> CREATOR =
new Parcelable.Creator<DateValidatorPointForward>() {
@Override
public DateValidatorPointForward createFromParcel(Parcel source) {
return new DateValidatorPointForward(source.readLong());
}

@Override
public DateValidatorPointForward[] newArray(int size) {
return new DateValidatorPointForward[size];
}
};

@Override
public boolean isValid(long date) {
return date >= point;
}

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(point);
}
}
61 changes: 33 additions & 28 deletions lib/java/com/google/android/material/picker/MaterialCalendar.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ enum CalendarSelector {

private static final String THEME_RES_ID_KEY = "THEME_RES_ID_KEY";
private static final String GRID_SELECTOR_KEY = "GRID_SELECTOR_KEY";
private static final String CALENDAR_BOUNDS_KEY = "CALENDAR_BOUNDS_KEY";
private static final String CALENDAR_CONSTRAINTS_KEY = "CALENDAR_CONSTRAINTS_KEY";

@VisibleForTesting
@RestrictTo(Scope.LIBRARY_GROUP)
public static final Object VIEW_PAGER_TAG = "VIEW_PAGER_TAG";

private int themeResId;
private GridSelector<S> gridSelector;
private CalendarBounds calendarBounds;
private CalendarConstraints calendarConstraints;
private CalendarSelector calendarSelector;
private CalendarStyle calendarStyle;
private RecyclerView yearSelector;
Expand All @@ -75,12 +75,12 @@ enum CalendarSelector {
private View dayFrame;

static <T> MaterialCalendar<T> newInstance(
GridSelector<T> gridSelector, int themeResId, CalendarBounds calendarBounds) {
GridSelector<T> gridSelector, int themeResId, CalendarConstraints calendarConstraints) {
MaterialCalendar<T> materialCalendar = new MaterialCalendar<>();
Bundle args = new Bundle();
args.putInt(THEME_RES_ID_KEY, themeResId);
args.putParcelable(GRID_SELECTOR_KEY, gridSelector);
args.putParcelable(CALENDAR_BOUNDS_KEY, calendarBounds);
args.putParcelable(CALENDAR_CONSTRAINTS_KEY, calendarConstraints);
materialCalendar.setArguments(args);
return materialCalendar;
}
Expand All @@ -90,7 +90,7 @@ public void onSaveInstanceState(@NonNull Bundle bundle) {
super.onSaveInstanceState(bundle);
bundle.putInt(THEME_RES_ID_KEY, themeResId);
bundle.putParcelable(GRID_SELECTOR_KEY, gridSelector);
bundle.putParcelable(CALENDAR_BOUNDS_KEY, calendarBounds);
bundle.putParcelable(CALENDAR_CONSTRAINTS_KEY, calendarConstraints);
}

@Override
Expand All @@ -99,7 +99,7 @@ public void onCreate(@Nullable Bundle bundle) {
Bundle activeBundle = bundle == null ? getArguments() : bundle;
themeResId = activeBundle.getInt(THEME_RES_ID_KEY);
gridSelector = activeBundle.getParcelable(GRID_SELECTOR_KEY);
calendarBounds = activeBundle.getParcelable(CALENDAR_BOUNDS_KEY);
calendarConstraints = activeBundle.getParcelable(CALENDAR_CONSTRAINTS_KEY);
}

@NonNull
Expand All @@ -112,7 +112,7 @@ public View onCreateView(
calendarStyle = new CalendarStyle(themedContext);
LayoutInflater themedInflater = layoutInflater.cloneInContext(themedContext);

Month earliestMonth = calendarBounds.getStart();
Month earliestMonth = calendarConstraints.getStart();

int layout;
int orientation;
Expand All @@ -139,15 +139,17 @@ public View onCreateView(
getChildFragmentManager(),
getLifecycle(),
gridSelector,
calendarBounds,
calendarConstraints,
day -> {
gridSelector.select(day);
for (OnSelectionChangedListener<S> listener : onSelectionChangedListeners) {
listener.onSelectionChanged(gridSelector.getSelection());
}
monthsPager.getAdapter().notifyDataSetChanged();
if (yearSelector != null) {
yearSelector.getAdapter().notifyDataSetChanged();
if (calendarConstraints.getDateValidator().isValid(day.getTimeInMillis())) {
gridSelector.select(day);
for (OnSelectionChangedListener<S> listener : onSelectionChangedListeners) {
listener.onSelectionChanged(gridSelector.getSelection());
}
monthsPager.getAdapter().notifyDataSetChanged();
if (yearSelector != null) {
yearSelector.getAdapter().notifyDataSetChanged();
}
}
});
monthsPager.setAdapter(monthsPagerAdapter);
Expand Down Expand Up @@ -220,22 +222,24 @@ public void onDraw(
};
}

/** Returns the {@link CalendarBounds} in use by this {@link MaterialCalendar}. */
CalendarBounds getCalendarBounds() {
return calendarBounds;
/** Returns the {@link CalendarConstraints} in use by this {@link MaterialCalendar}. */
CalendarConstraints getCalendarConstraints() {
return calendarConstraints;
}

/**
* Changes the currently displayed {@link Month} to {@code moveTo}.
*
* @throws IllegalArgumentException If {@code moveTo} is not within the allowed {@link
* CalendarBounds}.
* CalendarConstraints}.
*/
void setCurrentMonth(Month moveTo) {
calendarBounds =
CalendarBounds.create(calendarBounds.getStart(), calendarBounds.getEnd(), moveTo);
calendarConstraints =
CalendarConstraints.create(
calendarConstraints.getStart(), calendarConstraints.getEnd(), moveTo);
int moveToPosition =
((MonthsPagerAdapter) monthPager.getAdapter()).getPosition(calendarBounds.getCurrent());
((MonthsPagerAdapter) monthPager.getAdapter())
.getPosition(calendarConstraints.getCurrent());
monthPager.setCurrentItem(moveToPosition);
}

Expand Down Expand Up @@ -266,7 +270,7 @@ void setSelector(CalendarSelector selector) {
.getLayoutManager()
.scrollToPosition(
((YearGridAdapter) yearSelector.getAdapter())
.getPositionForYear(calendarBounds.getCurrent().year));
.getPositionForYear(calendarConstraints.getCurrent().year));
yearFrame.setVisibility(View.VISIBLE);
dayFrame.setVisibility(View.GONE);
} else if (selector == CalendarSelector.DAY) {
Expand Down Expand Up @@ -299,11 +303,12 @@ private void addActionsToMonthNavigation(
new OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
calendarBounds =
CalendarBounds.create(
calendarBounds.getStart(),
calendarBounds.getEnd(),
monthsPagerAdapter.getPageMonth(position));
calendarConstraints =
CalendarConstraints.create(
calendarConstraints.getStart(),
calendarConstraints.getEnd(),
monthsPagerAdapter.getPageMonth(position),
calendarConstraints.getDateValidator());
monthDropSelect.setText(monthsPagerAdapter.getPageTitle(position));
}
});
Expand Down
Loading

0 comments on commit fb44b90

Please sign in to comment.