Mapsme 14293 date picker constraints (#13543)

* [android] Fixed to use the last version of the material components in build gradle

* [android] Recovered dates constraints for new filter date controls in toolbar

* [android] Added checking if checkout date equals the checkin

* [android] Changed max checkin/checkout window in days constant value to correspond IOs implementation

* [android] Added checking on zero during calculating the checkout date
This commit is contained in:
Aleksandr Zatsepin 2020-08-13 15:51:10 +03:00 committed by GitHub
parent 1e4b4bef3b
commit e82df4d304
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 154 additions and 29 deletions

View file

@ -144,7 +144,7 @@ configurations.all {
force "com.google.firebase:firebase-measurement-connector:18.0.0"
force "com.google.firebase:firebase-iid-interop:17.0.0"
force "com.google.firebase:firebase-common:19.3.0"
force "com.google.android.material:material:1.1.0"
force "com.google.android.material:material:1.2.0"
force "androidx.constraintlayout:constraintlayout:1.1.3"
force "androidx.vectordrawable:vectordrawable:1.1.0"
force "androidx.coordinatorlayout:coordinatorlayout:1.1.0"

View file

@ -1,18 +1,36 @@
package com.mapswithme.maps.search;
import android.content.Context;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.datepicker.CalendarConstraints;
import com.google.android.material.datepicker.CompositeDateValidator;
import com.google.android.material.datepicker.DateValidatorPointBackward;
import com.google.android.material.datepicker.DateValidatorPointForward;
import com.google.android.material.datepicker.MaterialDatePicker;
import com.mapswithme.maps.R;
import com.mapswithme.util.Utils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
public class FilterUtils
{
private static final int MAX_STAYING_DAYS = 30;
private static final int MAX_CHECKIN_WINDOW_IN_DAYS = 365;
private static final String DAY_OF_MONTH_PATTERN = "MMM d";
@Retention(RetentionPolicy.SOURCE)
@IntDef({ RATING_ANY, RATING_GOOD, RATING_VERYGOOD, RATING_EXCELLENT })
public @interface RatingDef
@ -180,4 +198,66 @@ public class FilterUtils
List<HotelsFilter.HotelType> hotelTypes = new ArrayList<>(Arrays.asList(types));
return makeOneOf(hotelTypes.iterator());
}
public static long getMaxCheckoutInMillis(long checkinMillis)
{
long difference = checkinMillis - MaterialDatePicker.todayInUtcMilliseconds();
int daysToCheckin = (int) TimeUnit.MILLISECONDS.toDays(difference);
int leftDays = MAX_CHECKIN_WINDOW_IN_DAYS - daysToCheckin;
if (leftDays <= 0)
throw new AssertionError("No available dates for checkout!");
Calendar date = Utils.getCalendarInstance();
date.setTimeInMillis(checkinMillis);
date.add(Calendar.DAY_OF_YEAR, Math.min(leftDays, MAX_STAYING_DAYS));
return date.getTimeInMillis();
}
private static long getMaxCheckinInMillis()
{
final long today = MaterialDatePicker.todayInUtcMilliseconds();
Calendar calendar = Utils.getCalendarInstance();
calendar.setTimeInMillis(today);
calendar.add(Calendar.DAY_OF_YEAR, MAX_CHECKIN_WINDOW_IN_DAYS);
return calendar.getTimeInMillis();
}
@NonNull
public static CalendarConstraints.Builder createDateConstraintsBuilder()
{
final long today = MaterialDatePicker.todayInUtcMilliseconds();
CalendarConstraints.Builder constraintsBuilder = new CalendarConstraints.Builder();
constraintsBuilder.setStart(today);
constraintsBuilder.setEnd(getMaxCheckinInMillis());
List<CalendarConstraints.DateValidator> validators = new ArrayList<>();
validators.add(DateValidatorPointForward.now());
validators.add(DateValidatorPointBackward.before(getMaxCheckinInMillis()));
constraintsBuilder.setValidator(CompositeDateValidator.allOf(validators));
return constraintsBuilder;
}
@NonNull
public static String makeDateRangeHeader(@NonNull Context context, long checkinMillis,
long checkoutMillis)
{
final SimpleDateFormat dateFormater = new SimpleDateFormat(DAY_OF_MONTH_PATTERN,
Locale.getDefault());
String checkin = dateFormater.format(new Date(checkinMillis));
String checkout = dateFormater.format(new Date(checkoutMillis));
return context.getString(R.string.booking_filter_date_range, checkin, checkout);
}
public static boolean isWithinMaxStayingDays(long checkinMillis, long checkoutMillis)
{
long difference = checkoutMillis - checkinMillis;
int days = (int) TimeUnit.MILLISECONDS.toDays(difference);
return days <= MAX_STAYING_DAYS;
}
public static long getDayAfter(long date)
{
Calendar dayAfter = Utils.getCalendarInstance();
dayAfter.setTimeInMillis(date);
dayAfter.add(Calendar.DAY_OF_YEAR, 1);
return dayAfter.getTimeInMillis();
}
}

View file

@ -9,6 +9,7 @@ import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -16,27 +17,25 @@ import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.util.Pair;
import com.google.android.material.chip.Chip;
import com.google.android.material.datepicker.CalendarConstraints;
import com.google.android.material.datepicker.MaterialDatePicker;
import com.google.android.material.datepicker.MaterialPickerOnPositiveButtonClickListener;
import com.mapswithme.maps.R;
import com.mapswithme.maps.search.BookingFilterParams;
import com.mapswithme.maps.search.FilterUtils;
import com.mapswithme.util.InputUtils;
import com.mapswithme.util.StringUtils;
import com.mapswithme.util.UiUtils;
import com.mapswithme.util.statistics.AlohaHelper;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
public class SearchToolbarController extends ToolbarController
implements View.OnClickListener
{
private static final int REQUEST_VOICE_RECOGNITION = 0xCA11;
public static final String DAY_OF_MONTH_PATTERN = "MMM d";
@NonNull
private final View mSearchContainer;
@NonNull
@ -66,27 +65,20 @@ public class SearchToolbarController extends ToolbarController
};
@Nullable
private Pair<Long, Long> mChosenDates;
@NonNull
private final View.OnClickListener mChooseDatesClickListener = v -> {
MaterialDatePicker.Builder<Pair<Long, Long>> builder
= MaterialDatePicker.Builder.dateRangePicker();
CalendarConstraints.Builder constraintsBuilder = FilterUtils.createDateConstraintsBuilder();
builder.setCalendarConstraints(constraintsBuilder.build());
if (mChosenDates != null)
builder.setSelection(mChosenDates);
final MaterialDatePicker<?> picker = builder.build();
picker.addOnPositiveButtonClickListener(new MaterialPickerOnPositiveButtonClickListener<Object>()
{
@Override
public void onPositiveButtonClick(Object selection)
{
//noinspection unchecked
mChosenDates = (Pair<Long, Long>) selection;
mChooseDatesChip.setText(picker.getHeaderText());
for (FilterParamsChangedListener listener : mFilterParamsChangedListeners)
listener.onBookingParamsChanged();
}
});
final MaterialDatePicker<Pair<Long, Long>> picker = builder.build();
picker.addOnPositiveButtonClickListener(new DatePickerPositiveClickListener(picker));
picker.show(((AppCompatActivity) getActivity()).getSupportFragmentManager(), picker.toString());
};
@NonNull
private List<FilterParamsChangedListener> mFilterParamsChangedListeners = new ArrayList<>();
@ -133,19 +125,18 @@ public class SearchToolbarController extends ToolbarController
}
public void setFilterParams(@NonNull BookingFilterParams params)
{
formatAndSetChosenDates(params.getCheckinMillisec(), params.getCheckoutMillisec());
}
private void formatAndSetChosenDates(long checkinMillis, long checkoutMillis)
{
if (mChooseDatesChip == null)
return;
mChosenDates = new Pair<>(params.getCheckinMillisec(), params.getCheckoutMillisec());
SimpleDateFormat dateFormater = new SimpleDateFormat(DAY_OF_MONTH_PATTERN,
Locale.getDefault());
@SuppressWarnings("ConstantConditions")
String start = dateFormater.format(new Date(mChosenDates.first));
@SuppressWarnings("ConstantConditions")
String end = dateFormater.format(new Date(mChosenDates.second));
mChooseDatesChip.setText(getActivity().getString(R.string.booking_filter_date_range,
start, end));
mChooseDatesChip.setText(FilterUtils.makeDateRangeHeader(getActivity(), checkinMillis,
checkoutMillis));
mChosenDates = new Pair<>(checkinMillis, checkoutMillis);
}
public void resetFilterParams()
@ -320,4 +311,50 @@ public class SearchToolbarController extends ToolbarController
{
void onBookingParamsChanged();
}
private class DatePickerPositiveClickListener
implements MaterialPickerOnPositiveButtonClickListener<Pair<Long, Long>>
{
@NonNull
private final MaterialDatePicker<Pair<Long, Long>> mPicker;
private DatePickerPositiveClickListener(@NonNull MaterialDatePicker<Pair<Long, Long>> picker)
{
mPicker = picker;
}
@Override
public void onPositiveButtonClick(Pair<Long, Long> selection)
{
if (selection == null)
return;
mChosenDates = selection;
if (mChosenDates.first == null || mChosenDates.second == null)
return;
validateAndSetupDates(mChosenDates.first, mChosenDates.second);
for (FilterParamsChangedListener listener : mFilterParamsChangedListeners)
listener.onBookingParamsChanged();
}
private void validateAndSetupDates(long checkinMillis, long checkoutMillis)
{
if (checkoutMillis <= checkinMillis)
{
formatAndSetChosenDates(checkinMillis, FilterUtils.getDayAfter(checkinMillis));
}
else if (!FilterUtils.isWithinMaxStayingDays(checkinMillis, checkoutMillis))
{
Toast.makeText(getActivity(), R.string.thirty_days_limit_dialog, Toast.LENGTH_LONG).show();
formatAndSetChosenDates(checkinMillis, FilterUtils.getMaxCheckoutInMillis(checkinMillis));
}
else
{
Objects.requireNonNull(mChooseDatesChip);
mChooseDatesChip.setText(mPicker.getHeaderText());
}
}
}
}

View file

@ -34,8 +34,8 @@ import androidx.fragment.app.Fragment;
import com.mapswithme.maps.BuildConfig;
import com.mapswithme.maps.MwmApplication;
import com.mapswithme.maps.R;
import com.mapswithme.maps.base.CustomNavigateUpListener;
import com.mapswithme.maps.analytics.ExternalLibrariesMediator;
import com.mapswithme.maps.base.CustomNavigateUpListener;
import com.mapswithme.util.concurrency.UiThread;
import com.mapswithme.util.log.Logger;
import com.mapswithme.util.log.LoggerFactory;
@ -48,11 +48,13 @@ import java.net.NetworkInterface;
import java.security.MessageDigest;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Currency;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
public class Utils
{
@ -795,6 +797,12 @@ public class Utils
return getLocalizedFeatureByKey(context, key);
}
@NonNull
public static Calendar getCalendarInstance()
{
return Calendar.getInstance(TimeZone.getTimeZone("UTC"));
}
private static class SupportInfoWithLogsCallback implements LoggerFactory.OnZipCompletedListener
{
@NonNull