forked from organicmaps/organicmaps
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:
parent
1e4b4bef3b
commit
e82df4d304
4 changed files with 154 additions and 29 deletions
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue