diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 00000000000..adb3fe10c82 --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,2 @@ +jdk: + - openjdk11 diff --git a/lib/java/com/google/android/material/datepicker/DateSelector.java b/lib/java/com/google/android/material/datepicker/DateSelector.java index 690fef8b3bc..7e16aca3b40 100644 --- a/lib/java/com/google/android/material/datepicker/DateSelector.java +++ b/lib/java/com/google/android/material/datepicker/DateSelector.java @@ -66,6 +66,11 @@ public interface DateSelector extends Parcelable { */ void setSelection(@NonNull S selection); + /** + * Clears the current selection. + */ + void clearSelection(); + /** * Allows this selection handler to respond to clicks within the {@link AdapterView}. * diff --git a/lib/java/com/google/android/material/datepicker/MaterialCalendar.java b/lib/java/com/google/android/material/datepicker/MaterialCalendar.java index af9dd80430f..ed7bd8c9220 100644 --- a/lib/java/com/google/android/material/datepicker/MaterialCalendar.java +++ b/lib/java/com/google/android/material/datepicker/MaterialCalendar.java @@ -487,6 +487,19 @@ void sendAccessibilityFocusEventToMonthDropdown() { } } + public void clearSelection() { + if (dateSelector != null) { + // Clear the selection then call 'selection changed' listeners, if any + dateSelector.clearSelection(); + for (OnSelectionChangedListener listener : onSelectionChangedListeners) { + listener.onSelectionChanged(dateSelector.getSelection()); + } + + // Update the gridview + recyclerView.getAdapter().notifyDataSetChanged(); + } + } + void toggleVisibleSelector() { if (calendarSelector == CalendarSelector.YEAR) { setSelector(CalendarSelector.DAY); diff --git a/lib/java/com/google/android/material/datepicker/MaterialDatePicker.java b/lib/java/com/google/android/material/datepicker/MaterialDatePicker.java index 9f48d1a0800..900ca4e2a8c 100644 --- a/lib/java/com/google/android/material/datepicker/MaterialDatePicker.java +++ b/lib/java/com/google/android/material/datepicker/MaterialDatePicker.java @@ -39,6 +39,7 @@ import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.Window; import android.widget.Button; @@ -99,10 +100,13 @@ public class MaterialDatePicker extends DialogFragment { private static final String INPUT_MODE_KEY = "INPUT_MODE_KEY"; private static final String CALENDAR_FRAGMENT_TAG = "CALENDAR_FRAGMENT_TAG"; private static final String TEXT_INPUT_FRAGMENT_TAG = "TEXT_INPUT_FRAGMENT_TAG"; + private static final String CLEARABLE_KEY = "CLEARABLE_KEY"; + private static final String DISMISS_ON_CLEAR_KEY = "DISMISS_ON_CLEAR_KEY"; static final Object CONFIRM_BUTTON_TAG = "CONFIRM_BUTTON_TAG"; static final Object CANCEL_BUTTON_TAG = "CANCEL_BUTTON_TAG"; static final Object TOGGLE_BUTTON_TAG = "TOGGLE_BUTTON_TAG"; + static final Object CLEAR_BUTTON_TAG = "CLEAR_BUTTON_TAG"; /** Date picker will start with calendar view. */ public static final int INPUT_MODE_CALENDAR = 0; @@ -143,6 +147,8 @@ public String getHeaderText() { new LinkedHashSet<>(); private final LinkedHashSet onCancelListeners = new LinkedHashSet<>(); + private final LinkedHashSet onClearButtonClickListeners = + new LinkedHashSet<>(); private final LinkedHashSet onDismissListeners = new LinkedHashSet<>(); @@ -169,6 +175,10 @@ public String getHeaderText() { private CheckableImageButton headerToggleButton; @Nullable private MaterialShapeDrawable background; private Button confirmButton; + private Button headerConfirmButton; + private Button clearButton; + private boolean isClearable; + private boolean dismissOnClear; private boolean edgeToEdgeEnabled; @Nullable private CharSequence fullTitleText; @@ -199,6 +209,8 @@ static MaterialDatePicker newInstance(@NonNull Builder options) { options.negativeButtonContentDescriptionResId); args.putCharSequence( NEGATIVE_BUTTON_CONTENT_DESCRIPTION_KEY, options.negativeButtonContentDescription); + args.putBoolean(CLEARABLE_KEY, options.isClearable); + args.putBoolean(DISMISS_ON_CLEAR_KEY, options.dismissOnClear); materialDatePickerDialogFragment.setArguments(args); return materialDatePickerDialogFragment; } @@ -232,6 +244,8 @@ public final void onSaveInstanceState(@NonNull Bundle bundle) { NEGATIVE_BUTTON_CONTENT_DESCRIPTION_RES_ID_KEY, negativeButtonContentDescriptionResId); bundle.putCharSequence( NEGATIVE_BUTTON_CONTENT_DESCRIPTION_KEY, negativeButtonContentDescription); + bundle.putBoolean(CLEARABLE_KEY, isClearable); + bundle.putBoolean(DISMISS_ON_CLEAR_KEY, dismissOnClear); } @Override @@ -261,6 +275,8 @@ public final void onCreate(@Nullable Bundle bundle) { fullTitleText = titleText != null ? titleText : requireContext().getResources().getText(titleTextResId); singleLineTitleText = getFirstLineBySeparator(fullTitleText); + isClearable = activeBundle.getBoolean(CLEARABLE_KEY); + dismissOnClear = activeBundle.getBoolean(DISMISS_ON_CLEAR_KEY); } private int getThemeResId(Context context) { @@ -331,11 +347,7 @@ public final View onCreateView( initHeaderToggle(context); confirmButton = root.findViewById(R.id.confirm_button); - if (getDateSelector().isSelectionComplete()) { - confirmButton.setEnabled(true); - } else { - confirmButton.setEnabled(false); - } + confirmButton.setEnabled(getDateSelector().isSelectionComplete()); confirmButton.setTag(CONFIRM_BUTTON_TAG); if (positiveButtonText != null) { confirmButton.setText(positiveButtonText); @@ -364,6 +376,76 @@ public final View onCreateView( getContext().getResources().getText(negativeButtonContentDescriptionResId)); } cancelButton.setOnClickListener(this::onNegativeButtonClick); + + // In fullscreen mode we have a confirm and cancel button in the header as well + if (fullscreen) { + headerConfirmButton = root.findViewById(R.id.header_confirm_button); + headerConfirmButton.setEnabled(getDateSelector().isSelectionComplete()); + headerConfirmButton.setTag(CONFIRM_BUTTON_TAG); + if (positiveButtonText != null) { + headerConfirmButton.setText(positiveButtonText); + } else if (positiveButtonTextResId != 0) { + headerConfirmButton.setText(positiveButtonTextResId); + } + headerConfirmButton.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + for (MaterialPickerOnPositiveButtonClickListener listener : + onPositiveButtonClickListeners) { + listener.onPositiveButtonClick(getSelection()); + } + dismiss(); + } + }); + + Button headerCancelButton = root.findViewById(R.id.header_cancel_button); + headerCancelButton.setTag(CANCEL_BUTTON_TAG); + if (negativeButtonText != null) { + headerCancelButton.setText(negativeButtonText); + } else if (negativeButtonTextResId != 0) { + headerCancelButton.setText(negativeButtonTextResId); + } + headerCancelButton.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + for (View.OnClickListener listener : onNegativeButtonClickListeners) { + listener.onClick(v); + } + dismiss(); + } + }); + } + + clearButton = root.findViewById(R.id.clear_button); + clearButton.setTag(CLEAR_BUTTON_TAG); + if (isClearable) { + clearButton.setEnabled(getDateSelector().isSelectionComplete()); + clearButton.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + // Clear the selection + calendar.clearSelection(); + + // Call 'cleared' listeners, if any + for (View.OnClickListener listener : onClearButtonClickListeners) { + listener.onClick(v); + } + + if (dismissOnClear) { + dismiss(); + } + } + }); + + clearButton.setVisibility(View.VISIBLE); + + } else { + clearButton.setVisibility(View.GONE); + } + return root; } @@ -531,12 +613,14 @@ private void startPickerFragment() { @Override public void onSelectionChanged(S selection) { updateHeader(getHeaderText()); - confirmButton.setEnabled(getDateSelector().isSelectionComplete()); + updateActionButtonEnabledState(); } @Override public void onIncompleteSelectionChanged() { confirmButton.setEnabled(false); + if (fullscreen) headerConfirmButton.setEnabled(false); + clearButton.setEnabled(false); } }); @@ -562,7 +646,7 @@ private void initHeaderToggle(Context context) { headerToggleButton.setOnClickListener( v -> { // Update confirm button in case in progress selection has been reset - confirmButton.setEnabled(getDateSelector().isSelectionComplete()); + updateActionButtonEnabledState(); headerToggleButton.toggle(); inputMode = (inputMode == INPUT_MODE_TEXT) ? INPUT_MODE_CALENDAR : INPUT_MODE_TEXT; @@ -572,6 +656,12 @@ private void initHeaderToggle(Context context) { }); } + private void updateActionButtonEnabledState() { + confirmButton.setEnabled(getDateSelector().isSelectionComplete()); + if (fullscreen) headerConfirmButton.setEnabled(getDateSelector().isSelectionComplete()); + clearButton.setEnabled(isClearable && getDateSelector().isSelectionComplete()); + } + private void updateToggleContentDescription(@NonNull CheckableImageButton toggle) { String contentDescription = inputMode == INPUT_MODE_TEXT @@ -696,6 +786,27 @@ public void clearOnNegativeButtonClickListeners() { onNegativeButtonClickListeners.clear(); } + /** The supplied listener is called when the user clicks the clear button. */ + public boolean addOnClearButtonClickListener( + @NonNull OnClickListener onClearButtonClickListener) { + return onClearButtonClickListeners.add(onClearButtonClickListener); + } + + /** + * Removes a listener previously added via {@link MaterialDatePicker#addOnClearButtonClickListener}. + */ + public boolean removeOnClearButtonClickListener( + @NonNull OnClickListener onClearButtonClickListener) { + return onClearButtonClickListeners.remove(onClearButtonClickListener); + } + + /** + * Removes all listeners added via {@link MaterialDatePicker#addOnClearButtonClickListener}. + */ + public void clearOnClearButtonClickListeners() { + onClearButtonClickListeners.clear(); + } + /** * The supplied listener is called when the user cancels the picker via back button or a touch * outside the view. It is not called when the user clicks the cancel button. To add a listener @@ -754,6 +865,8 @@ public static final class Builder { CharSequence negativeButtonContentDescription = null; @Nullable S selection = null; @InputMode int inputMode = INPUT_MODE_CALENDAR; + boolean isClearable = false; + boolean dismissOnClear = false; private Builder(DateSelector dateSelector) { this.dateSelector = dateSelector; @@ -975,6 +1088,20 @@ public Builder setInputMode(@InputMode int inputMode) { return this; } + /** Sets the state of the clear button */ + @NonNull + public Builder setClearable(boolean clearable) { + this.isClearable = clearable; + return this; + } + + /** Sets the state of the dialog dismiss state when clear button is clicked */ + @NonNull + public Builder setDismissOnClear(boolean dismiss) { + this.dismissOnClear = dismiss; + return this; + } + /** Creates a {@link MaterialDatePicker} with the provided options. */ @NonNull public MaterialDatePicker build() { diff --git a/lib/java/com/google/android/material/datepicker/RangeDateSelector.java b/lib/java/com/google/android/material/datepicker/RangeDateSelector.java index 2fa805afc89..7eaccd66206 100644 --- a/lib/java/com/google/android/material/datepicker/RangeDateSelector.java +++ b/lib/java/com/google/android/material/datepicker/RangeDateSelector.java @@ -83,6 +83,12 @@ public boolean isSelectionComplete() { && isValidRange(selectedStartItem, selectedEndItem); } + @Override + public void clearSelection() { + selectedStartItem = null; + selectedEndItem = null; + } + @Override public void setSelection(@NonNull Pair selection) { if (selection.first != null && selection.second != null) { diff --git a/lib/java/com/google/android/material/datepicker/SingleDateSelector.java b/lib/java/com/google/android/material/datepicker/SingleDateSelector.java index 447ecfeb5cf..2578f4cd1e4 100644 --- a/lib/java/com/google/android/material/datepicker/SingleDateSelector.java +++ b/lib/java/com/google/android/material/datepicker/SingleDateSelector.java @@ -58,7 +58,8 @@ public void select(long selection) { selectedItem = selection; } - private void clearSelection() { + @Override + public void clearSelection() { selectedItem = null; } diff --git a/lib/java/com/google/android/material/datepicker/res/layout/mtrl_picker_actions.xml b/lib/java/com/google/android/material/datepicker/res/layout/mtrl_picker_actions.xml index 3d5e9deacbb..c4c61adcc9e 100644 --- a/lib/java/com/google/android/material/datepicker/res/layout/mtrl_picker_actions.xml +++ b/lib/java/com/google/android/material/datepicker/res/layout/mtrl_picker_actions.xml @@ -24,6 +24,20 @@ android:gravity="end" android:orientation="horizontal"> +