package de.westnordost.streetcomplete.quests.opening_hours;
import android.content.Context;
import android.content.DialogInterface;
import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import de.westnordost.streetcomplete.R;
import de.westnordost.streetcomplete.data.meta.CurrentCountry;
import de.westnordost.streetcomplete.data.meta.WorkWeek;
import de.westnordost.streetcomplete.view.dialogs.AlertDialogBuilder;
import de.westnordost.streetcomplete.view.dialogs.RangePickerDialog;
public class AddOpeningHoursAdapter extends RecyclerView.Adapter
{
private final static int MONTHS = 0, WEEKDAYS = 1;
private ArrayList<OpeningMonths> data;
private final Context context;
private final CurrentCountry currentCountry;
private boolean displayMonths = false;
public AddOpeningHoursAdapter(ArrayList<OpeningMonths> data, Context context, CurrentCountry currentCountry)
{
this.data = data;
this.context = context;
this.currentCountry = currentCountry;
}
public String toString()
{
return TextUtils.join(", ", data);
}
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
switch (viewType)
{
case MONTHS:
return new MonthsViewHolder(
inflater.inflate(R.layout.quest_opening_hours_month_row, parent, false));
case WEEKDAYS:
return new WeekdayViewHolder(
inflater.inflate(R.layout.quest_opening_hours_weekday_row, parent, false));
}
return null;
}
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
int[] p = getHierarchicPosition(position);
OpeningMonths om = data.get(p[0]);
if(holder instanceof MonthsViewHolder)
{
((MonthsViewHolder) holder).update(om, p[0]);
}
else if(holder instanceof WeekdayViewHolder)
{
OpeningWeekdays ow = om.weekdaysList.get(p[1]);
OpeningWeekdays prevOw = null;
if(p[1] > 0) prevOw = om.weekdaysList.get(p[1] - 1);
((WeekdayViewHolder) holder).update(ow, prevOw, p[1]);
}
}
@Override public int getItemViewType(int position)
{
int[] p = getHierarchicPosition(position);
return p.length == 1 ? MONTHS : WEEKDAYS;
}
private int[] getHierarchicPosition(int position)
{
int count = 0;
for (int i = 0; i < data.size(); ++i)
{
OpeningMonths om = data.get(i);
if(count == position) return new int[]{i};
++count;
for (int j = 0; j < om.weekdaysList.size(); ++j)
{
OpeningWeekdays ow = om.weekdaysList.get(j);
if(count == position) return new int[]{i,j};
++count;
}
}
throw new IllegalArgumentException();
}
@Override public int getItemCount()
{
int count = 0;
for (OpeningMonths om : data)
{
count += om.weekdaysList.size();
}
count += data.size();
return count;
}
/* ------------------------------------------------------------------------------------------ */
private void remove(int position)
{
int[] p = getHierarchicPosition(position);
if(p.length == 1)
{
OpeningMonths om = data.remove(p[0]);
notifyItemRangeRemoved(position, 1 + om.weekdaysList.size());
}
else if(p.length == 2)
{
ArrayList<OpeningWeekdays> weekdays = data.get(p[0]).weekdaysList;
OpeningWeekdays ow = weekdays.remove(p[1]);
notifyItemRemoved(position);
// if not last weekday removed -> element after this one may need to be updated
// because it may need to show the weekdays now
if(p[1] < weekdays.size()) notifyItemChanged(position);
}
}
public void addNewMonths()
{
// Welcome! Welcome to callback hell! (well, it's not that bad if you let IntelliJ fold it)
openSetMonthsRangeDialog(getMonthsRangeSuggestion(), new RangePickerDialog.OnRangeChangeListener()
{
@Override public void onRangeChange(int startIndex, int endIndex)
{
final CircularSection months = new CircularSection(startIndex, endIndex);
openSetWeekdaysDialog(getWeekdaysSuggestion(true), new WeekdaysPickedListener()
{
@Override public void onWeekdaysPicked(final Weekdays weekdays)
{
openSetTimeRangeDialog(getOpeningHoursSuggestion(), new TimeRangePickerDialog.OnTimeRangeChangeListener()
{
@Override public void onTimeRangeChange(final TimeRange timeRange)
{
addMonths(months, weekdays, timeRange);
}
});
}
});
}
});
}
private void addMonths(CircularSection months, Weekdays weekdays, TimeRange timeRange)
{
int insertIndex = getItemCount();
data.add(new OpeningMonths(months, new OpeningWeekdays(weekdays, timeRange)));
notifyItemRangeInserted(insertIndex, 2); // 2 = opening month + opening weekday
}
public void addNewWeekdays()
{
boolean isFirst = data.get(data.size()-1).weekdaysList.isEmpty();
openSetWeekdaysDialog(getWeekdaysSuggestion(isFirst), new WeekdaysPickedListener()
{
@Override public void onWeekdaysPicked(final Weekdays weekdays)
{
openSetTimeRangeDialog(getOpeningHoursSuggestion(), new TimeRangePickerDialog.OnTimeRangeChangeListener()
{
@Override public void onTimeRangeChange(final TimeRange timeRange)
{
addWeekdays(weekdays, timeRange);
}
});
}
});
}
private void addWeekdays(Weekdays weekdays, TimeRange timeRange)
{
int insertIndex = getItemCount();
data.get(data.size()-1).weekdaysList.add(new OpeningWeekdays(weekdays, timeRange));
notifyItemInserted(insertIndex);
}
public ArrayList<OpeningMonths> getData()
{
return data;
}
public void setDisplayMonths(boolean displayMonths)
{
this.displayMonths = displayMonths;
notifyDataSetChanged();
}
public void changeToMonthsMode()
{
setDisplayMonths(true);
final OpeningMonths om = data.get(0);
openSetMonthsRangeDialog(om.months, new RangePickerDialog.OnRangeChangeListener()
{
@Override public void onRangeChange(int startIndex, int endIndex)
{
om.months = new CircularSection(startIndex, endIndex);
notifyItemChanged(0);
}
});
}
/* -------------------------------------- months select --------------------------------------*/
private class MonthsViewHolder extends RecyclerView.ViewHolder
{
private TextView monthsText;
private View delete;
public MonthsViewHolder(View itemView)
{
super(itemView);
monthsText = (TextView) itemView.findViewById(R.id.months_from_to);
delete = itemView.findViewById(R.id.delete);
delete.setOnClickListener(new View.OnClickListener()
{
@Override public void onClick(View v)
{
int index = getAdapterPosition();
if(index != RecyclerView.NO_POSITION) remove(getAdapterPosition());
}
});
}
public void setVisibility(boolean isVisible)
{
RecyclerView.LayoutParams param = (RecyclerView.LayoutParams)itemView.getLayoutParams();
if (isVisible)
{
param.height = LinearLayout.LayoutParams.WRAP_CONTENT;
param.width = LinearLayout.LayoutParams.MATCH_PARENT;
itemView.setVisibility(View.VISIBLE);
}
else
{
itemView.setVisibility(View.GONE);
param.height = 0;
param.width = 0;
}
itemView.setLayoutParams(param);
}
public void update(final OpeningMonths data, int index)
{
setVisibility(displayMonths);
delete.setVisibility(index==0 ? View.GONE : View.VISIBLE);
monthsText.setText(data.getLocalizedMonthsString());
monthsText.setOnClickListener(new View.OnClickListener()
{
@Override public void onClick(View v)
{
openSetMonthsRangeDialog(data.months, new RangePickerDialog.OnRangeChangeListener()
{
@Override public void onRangeChange(int startIndex, int endIndex)
{
data.months = new CircularSection(startIndex, endIndex);
notifyItemChanged(getAdapterPosition());
}
});
}
});
}
}
private @NonNull CircularSection getMonthsRangeSuggestion()
{
List<CircularSection> months = getUnmentionedMonths();
if(months.isEmpty())
{
return new CircularSection(0,OpeningMonths.MAX_MONTH_INDEX);
}
return months.get(0);
}
private List<CircularSection> getUnmentionedMonths()
{
List<CircularSection> allTheMonths = new ArrayList<>();
for (OpeningMonths om : data)
{
allTheMonths.add(om.months);
}
return new NumberSystem(0,OpeningMonths.MAX_MONTH_INDEX).complemented(allTheMonths);
}
private void openSetMonthsRangeDialog(CircularSection months,
RangePickerDialog.OnRangeChangeListener callback )
{
String[] monthNames = DateFormatSymbols.getInstance().getMonths();
String selectMonths = context.getResources().getString(R.string.quest_openingHours_chooseMonthsTitle);
new RangePickerDialog(context, callback, monthNames, months.getStart(),
months.getEnd(), selectMonths).show();
}
/* ------------------------------------ weekdays select --------------------------------------*/
private class WeekdayViewHolder extends RecyclerView.ViewHolder
{
private TextView weekdaysText;
private TextView hoursText;
private View delete;
public WeekdayViewHolder(View itemView)
{
super(itemView);
weekdaysText = (TextView) itemView.findViewById(R.id.weekday_from_to);
hoursText = (TextView) itemView.findViewById(R.id.hours_from_to);
delete = itemView.findViewById(R.id.delete);
delete.setOnClickListener(new View.OnClickListener()
{
@Override public void onClick(View v)
{
int index = getAdapterPosition();
if(index != RecyclerView.NO_POSITION) remove(getAdapterPosition());
}
});
}
public void update(final OpeningWeekdays data, final OpeningWeekdays previousData, int index)
{
delete.setVisibility(index==0 ? View.GONE : View.VISIBLE);
if(previousData != null && data.weekdays.equals(previousData.weekdays))
{
weekdaysText.setText("");
}
else
{
weekdaysText.setText(data.weekdays.toLocalizedString(context.getResources()));
}
weekdaysText.setOnClickListener(new View.OnClickListener()
{
@Override public void onClick(View v)
{
openSetWeekdaysDialog(data.weekdays, new WeekdaysPickedListener()
{
@Override public void onWeekdaysPicked(Weekdays weekdays)
{
data.weekdays = weekdays;
notifyItemChanged(getAdapterPosition());
}
});
}
});
hoursText.setText(data.timeRange.toStringUsing("–"));
hoursText.setOnClickListener(new View.OnClickListener()
{
@Override public void onClick(View v)
{
openSetTimeRangeDialog(data.timeRange, new TimeRangePickerDialog.OnTimeRangeChangeListener()
{
@Override public void onTimeRangeChange(TimeRange timeRange)
{
data.timeRange = timeRange;
notifyItemChanged(getAdapterPosition());
}
});
}
});
}
}
private @NonNull Weekdays getWeekdaysSuggestion(boolean isFirst)
{
if(isFirst)
{
Locale locale = currentCountry.getLocale();
int firstWorkDayIdx = modulus(WorkWeek.getFirstDay(locale) - 2,7);
boolean[] result = new boolean[7];
for(int i = 0; i < WorkWeek.getRegularShoppingDays(locale); ++i)
{
result[(i + firstWorkDayIdx) % 7] = true;
}
return new Weekdays(result);
}
return new Weekdays();
}
private static int modulus(int a, int b)
{
return (a % b + b) % b;
}
private void openSetWeekdaysDialog(final Weekdays weekdays, final WeekdaysPickedListener callback)
{
final boolean[] selection = weekdays.getSelection();
AlertDialog dlg = new AlertDialogBuilder(context)
.setTitle(R.string.quest_openingHours_chooseWeekdaysTitle)
.setMultiChoiceItems(Weekdays.getNames(context.getResources()), selection, new DialogInterface.OnMultiChoiceClickListener()
{
@Override public void onClick(DialogInterface dialog, int which, boolean isChecked)
{
updateDialogOkButtonEnablement((AlertDialog) dialog, selection);
}
})
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
{
@Override public void onClick(DialogInterface dialog, int which)
{
callback.onWeekdaysPicked(new Weekdays(selection));
}
})
.show();
updateDialogOkButtonEnablement(dlg, selection);
}
private void updateDialogOkButtonEnablement(AlertDialog dlg, boolean[] selection)
{
boolean isAnyChecked = false;
for(boolean b : selection) isAnyChecked |= b;
dlg.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(isAnyChecked);
}
private interface WeekdaysPickedListener
{
void onWeekdaysPicked(Weekdays selected);
}
/* ------------------------------------- times select ----------------------------------------*/
// this could go into per-country localization when this page (or any other source)
// https://en.wikipedia.org/wiki/Shopping_hours contains more information about typical
// opening hours per country
private static final TimeRange TYPICAL_OPENING_TIMES = new TimeRange(8 * 60, 18 * 60, false);
private void openSetTimeRangeDialog(TimeRange timeRange,
TimeRangePickerDialog.OnTimeRangeChangeListener callback)
{
String startLabel = context.getResources().getString(R.string.quest_openingHours_start_time);
String endLabel = context.getResources().getString(R.string.quest_openingHours_end_time);
new TimeRangePickerDialog(context, callback, startLabel, endLabel, timeRange).show();
}
private @NonNull TimeRange getOpeningHoursSuggestion()
{
return TYPICAL_OPENING_TIMES;
}
}