package io.github.memfis19.cadar.internal.ui.list.adapter; import android.os.Handler; import android.support.v4.util.Pair; import android.support.v7.util.DiffUtil; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.TreeSet; import io.github.memfis19.cadar.data.entity.Event; import io.github.memfis19.cadar.event.DisplayEventCallback; import io.github.memfis19.cadar.event.OnDayChangeListener; import io.github.memfis19.cadar.event.OnEventClickListener; import io.github.memfis19.cadar.event.OnMonthChangeListener; import io.github.memfis19.cadar.internal.helper.ScrollManager; import io.github.memfis19.cadar.internal.process.EventsProcessor; import io.github.memfis19.cadar.internal.process.EventsProcessorCallback; import io.github.memfis19.cadar.internal.process.ListEventsProcessor; import io.github.memfis19.cadar.internal.ui.list.adapter.holder.EventHolder; import io.github.memfis19.cadar.internal.ui.list.adapter.holder.ListHolder; import io.github.memfis19.cadar.internal.ui.list.adapter.holder.MonthHolder; import io.github.memfis19.cadar.internal.ui.list.adapter.holder.WeekHolder; import io.github.memfis19.cadar.internal.ui.list.adapter.model.ListItemModel; import io.github.memfis19.cadar.internal.ui.list.event.EndlessRecyclerViewScrollListener; import io.github.memfis19.cadar.internal.utils.CalendarHelper; import io.github.memfis19.cadar.internal.utils.DateUtils; import io.github.memfis19.cadar.settings.ListCalendarConfiguration; import io.github.memfis19.cadar.view.ListCalendar; /** * Created by memfis on 9/5/16. */ public class ListAdapter extends RecyclerView.Adapter<ListHolder> implements ListCalendar.OnSetLayoutManagerListener { private static final String TAG = "ListAdapter"; private static final int THRESHOLD = 5; private ListCalendar calendarListView; private List<ListItemModel> listItemModels; private Handler backgroundHandler; private Handler uiHandler; private EventsProcessor<Pair<Calendar, Calendar>, List<Event>> listEventsAsyncProcessor; private int position; private Calendar startPeriod; private Calendar endPeriod; private ListCalendarConfiguration configuration; private OnMonthChangeListener monthChangeListener; private OnDayChangeListener dayChangeListener; private OnEventClickListener onEventClickListener; private List<Event> eventList = new ArrayList<>(); private DisplayEventCallback<Pair<Calendar, Calendar>> callback; public ListAdapter(ListCalendarConfiguration configuration, ListCalendar calendarListView, List<ListItemModel> listItemModels, List<Event> eventList, Calendar startPeriod, Calendar endPeriod, Handler backgroundHandler, Handler uiHandler, OnMonthChangeListener monthChangeLister, OnDayChangeListener dayChangeListener) { this.calendarListView = calendarListView; this.listItemModels = listItemModels; this.backgroundHandler = backgroundHandler; this.uiHandler = uiHandler; this.startPeriod = startPeriod; this.endPeriod = endPeriod; this.configuration = configuration; this.eventList = eventList; this.monthChangeListener = monthChangeLister; this.dayChangeListener = dayChangeListener; if (configuration.getEventsProcessor() != null) listEventsAsyncProcessor = configuration.getEventsProcessor(); else listEventsAsyncProcessor = new ListEventsProcessor(configuration.isEventProcessingEnabled(), configuration.getEventCalculator()); listEventsAsyncProcessor.setScrollManager(ScrollManager.geViewInstance(calendarListView)); listEventsAsyncProcessor.setEventProcessor(configuration.getEventCalculator()); listEventsAsyncProcessor.setEvents(eventList); listEventsAsyncProcessor.start(); listEventsAsyncProcessor.getLooper(); } private void loadMoreEvents() { Calendar tmp = (Calendar) endPeriod.clone(); endPeriod.add(configuration.getPeriodType(), configuration.getPeriodValue()); Calendar start = tmp; CalendarHelper.prepareListItems(listItemModels, start, DateUtils.monthBetweenPure(tmp.getTime(), endPeriod.getTime())); listEventsAsyncProcessor.setEventsProcessorCallback(new DefaultEventsProcessorCallback(true)); listEventsAsyncProcessor.queueEventsProcess(new Pair<>(start, endPeriod)); } private void loadBefore() { Calendar tmp = (Calendar) startPeriod.clone(); startPeriod.add(configuration.getPeriodType(), configuration.getPeriodValue() * -1); Calendar end = tmp; CalendarHelper.prepareListItems(listItemModels, startPeriod, DateUtils.monthBetweenPure(startPeriod.getTime(), end.getTime())); listEventsAsyncProcessor.setEventsProcessorCallback(new DefaultEventsProcessorCallback(true, true)); listEventsAsyncProcessor.queueEventsProcess(new Pair<>(startPeriod, end)); } private class DefaultEventsProcessorCallback implements EventsProcessorCallback<Pair<Calendar, Calendar>, List<Event>> { private boolean processCallback = false; private boolean keepPosition = false; DefaultEventsProcessorCallback(boolean processCallback) { this.processCallback = processCallback; } DefaultEventsProcessorCallback(boolean processCallback, boolean keepPosition) { this.processCallback = processCallback; this.keepPosition = keepPosition; } @Override public void onEventsProcessed(final Pair<Calendar, Calendar> target, final List<Event> result) { backgroundHandler.post(new Runnable() { @Override public void run() { final Set<ListItemModel> newItems = new TreeSet<>(getComparator()); newItems.addAll(listItemModels); for (Event event : result) { newItems.add(new ListItemModel(event.getEventStartDate(), event, ListItemModel.EVENT)); } final Calendar previousDate = getCurrentDate(); listItemModels.clear(); listItemModels.addAll(newItems); uiHandler.post(new Runnable() { @Override public void run() { notifyDataSetChanged(); if (keepPosition) setSelectedMonth(previousDate); if (processCallback && callback != null) callback.onEventsDisplayed(target); } }); } }); } } public void displayEvents() { displayEvents(new ArrayList<>(eventList), null); } public void displayEvents(List<Event> events, final DisplayEventCallback<Pair<Calendar, Calendar>> callback) { this.callback = callback; if (this.eventList == null) this.eventList = new ArrayList<>(); eventList.clear(); eventList.addAll(events); listEventsAsyncProcessor.setEvents(events); position = ((LinearLayoutManager) calendarListView.getLayoutManager()).findLastVisibleItemPosition(); listEventsAsyncProcessor.setEventsProcessorCallback(new EventsProcessorCallback<Pair<Calendar, Calendar>, List<Event>>() { @Override public void onEventsProcessed(final Pair<Calendar, Calendar> target, final List<Event> result) { listEventsAsyncProcessor.setEvents(eventList); backgroundHandler.post(new Runnable() { @Override public void run() { final List<ListItemModel> freshListItemModels = new ArrayList<>(); freshListItemModels.addAll(listItemModels); for (int i = freshListItemModels.size() - 1; i >= 0; i--) { if (freshListItemModels.get(i).getType() == ListItemModel.EVENT) { freshListItemModels.remove(i); } } for (Event event : result) { freshListItemModels.add(new ListItemModel(event.getEventStartDate(), event, ListItemModel.EVENT)); } sortListItemsAscending(freshListItemModels); final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() { @Override public int getOldListSize() { return listItemModels.size(); } @Override public int getNewListSize() { return freshListItemModels.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { ListItemModel oldItem = listItemModels.get(oldItemPosition); ListItemModel newItem = freshListItemModels.get(newItemPosition); if (oldItem.getType() != newItem.getType()) { return false; } else if ((oldItem.getType() == ListItemModel.WEEK && newItem.getType() == ListItemModel.WEEK) || (oldItem.getType() == ListItemModel.MONTH && newItem.getType() == ListItemModel.MONTH)) { return oldItem.getCalendar().getTimeInMillis() == newItem.getCalendar().getTimeInMillis(); } else { Event oldEvent = (Event) oldItem.getValue(); Event newEvent = (Event) newItem.getValue(); return oldEvent.getEventId().equals(newEvent.getEventId()) && oldEvent.getEventStartDate().equals(newEvent.getEventStartDate()); } } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { ListItemModel oldItem = listItemModels.get(oldItemPosition); ListItemModel newItem = freshListItemModels.get(newItemPosition); if (oldItem.getType() == ListItemModel.EVENT && newItem.getType() == ListItemModel.EVENT) { Event oldEvent = (Event) oldItem.getValue(); Event newEvent = (Event) newItem.getValue(); return oldEvent.getEventTitle().equals(newEvent.getEventTitle()) && oldEvent.getCalendarId().longValue() == newEvent.getCalendarId().longValue(); } else return true; } }, false); listItemModels.clear(); listItemModels.addAll(freshListItemModels); uiHandler.post(new Runnable() { @Override public void run() { diffResult.dispatchUpdatesTo(ListAdapter.this); if (callback != null) callback.onEventsDisplayed(target); } }); } }); } }); listEventsAsyncProcessor.queueEventsProcess(new Pair<>(startPeriod, endPeriod)); } public void addEvent(Event event) { if (this.eventList == null) this.eventList = new ArrayList<>(); eventList.add(event); listEventsAsyncProcessor.setEvents(Collections.singletonList(event)); listEventsAsyncProcessor.setEventsProcessorCallback(new DefaultEventsProcessorCallback(false)); listEventsAsyncProcessor.queueEventsProcess(new Pair<>(startPeriod, endPeriod)); } public void addEvents(List<Event> events) { if (this.eventList == null) this.eventList = new ArrayList<>(); eventList.addAll(events); listEventsAsyncProcessor.setEvents(events); listEventsAsyncProcessor.setEventsProcessorCallback(new DefaultEventsProcessorCallback(false)); listEventsAsyncProcessor.queueEventsProcess(new Pair<>(startPeriod, endPeriod)); } public void editEvent(Event event) { removeEvent(event); event.setEventStartDate(event.getOriginalEventStartDate()); addEvent(event); } public void removeEvent(Event event) { long id = event.getEventId(); for (int i = 0; i < listItemModels.size(); ++i) { ListItemModel listItemModel = listItemModels.get(i); if (listItemModel.getType() == ListItemModel.EVENT) { long currentId = ((Event) listItemModel.getValue()).getEventId(); if (id == currentId) { listItemModels.remove(i); notifyItemRemoved(i); --i; } } } for (int i = 0; i < eventList.size(); ++i) { if (eventList.get(i).getEventId().equals(id)) { eventList.remove(i); --i; } } } public void setSelectedMonth(final Calendar selectedMonth) { backgroundHandler.post(new Runnable() { @Override public void run() { position = getDatePosition(selectedMonth); if (position + selectedMonth.getActualMaximum(Calendar.DAY_OF_MONTH) > listItemModels.size()) { loadMoreEvents(); } uiHandler.post(new Runnable() { @Override public void run() { scrollToPosition(position); } }); } }); } private void scrollToPosition(int position) { ((LinearLayoutManager) calendarListView.getLayoutManager()).scrollToPositionWithOffset(position, 0); } public void setSelectedDay(final Calendar selectedDay) { backgroundHandler.post(new Runnable() { @Override public void run() { position = getDatePosition(DateUtils.setTimeToMidnight(selectedDay)); if (position + THRESHOLD >= listItemModels.size()) loadMoreEvents(); uiHandler.post(new Runnable() { @Override public void run() { scrollToPosition(position); } }); } }); } public void release() { listEventsAsyncProcessor.quit(); listItemModels.clear(); ScrollManager.geViewInstance(calendarListView).releaseScrollManager(); } @Override public ListHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view; ListHolder holder; if (viewType == ListItemModel.EVENT) { view = LayoutInflater.from(parent.getContext()) .inflate(configuration.getEventLayoutId(), parent, false); holder = new EventHolder(view, backgroundHandler, uiHandler, onEventClickListener, configuration.getEventDecoratorFactory()); holder.setType(ListItemModel.EVENT); } else if (viewType == ListItemModel.WEEK) { view = LayoutInflater.from(parent.getContext()) .inflate(configuration.getWeekLayoutId(), parent, false); holder = new WeekHolder(view, backgroundHandler, uiHandler, configuration.getWeekDecoratorFactory()); holder.setType(ListItemModel.WEEK); } else { view = LayoutInflater.from(parent.getContext()) .inflate(configuration.getMonthLayoutId(), parent, false); holder = new MonthHolder(calendarListView, view, backgroundHandler, uiHandler, configuration.getMonthDecoratorFactory()); holder.setType(ListItemModel.MONTH); } return holder; } @Override public void onBindViewHolder(ListHolder holder, int position) { if (holder.getType() == ListItemModel.EVENT) { ((EventHolder) holder).bindView((Event) listItemModels.get(position).getValue(), position > 0 ? listItemModels.get(position - 1) : null, position); } else if (holder.getType() == ListItemModel.WEEK) { ((WeekHolder) holder).bindView((Pair<Calendar, Calendar>) listItemModels.get(position).getValue()); } else if (holder.getType() == ListItemModel.MONTH) { ((MonthHolder) holder).bindView((Calendar) listItemModels.get(position).getValue()); } } @Override public void onViewDetachedFromWindow(ListHolder holder) { super.onViewDetachedFromWindow(holder); if (holder instanceof MonthHolder) { ((MonthHolder) holder).detach(); } } @Override public int getItemCount() { return listItemModels.size(); } private Calendar getCurrentDate() { int position = getFirstVisibleItemPosition(); return listItemModels.get(position > 0 ? position : 0).getCalendar(); } private int getFirstVisibleItemPosition() { return ((LinearLayoutManager) calendarListView.getLayoutManager()).findFirstVisibleItemPosition(); } private int getCurrentPosition() { int lastVisiblePosition = ((LinearLayoutManager) calendarListView.getLayoutManager()).findLastVisibleItemPosition(); return getDatePosition(listItemModels.get(lastVisiblePosition).getCalendar()); } public int getDatePosition(Calendar calendar) { long time = calendar.getTimeInMillis(); long itemTime = 0; long nextItemTime = 0; int scrollPosition = 0; for (int i = 0; i < listItemModels.size(); ++i) { itemTime = listItemModels.get(i).getCalendar().getTimeInMillis(); if (i + 1 < listItemModels.size()) nextItemTime = listItemModels.get(i + 1).getCalendar().getTimeInMillis(); else nextItemTime = listItemModels.get(listItemModels.size() - 1).getCalendar().getTimeInMillis(); if (time >= itemTime && time < nextItemTime) { scrollPosition = i; break; } } if (scrollPosition == 0) scrollPosition = listItemModels.size() - 1; return scrollPosition; } @Override public int getItemViewType(int position) { return listItemModels.get(position).getType(); } @Override public void onSetLayoutManager(RecyclerView.LayoutManager layout) { calendarListView.addOnScrollListener(new EndlessRecyclerViewScrollListener((LinearLayoutManager) layout, THRESHOLD) { @Override public void onLoadMore(int page, int totalItemsCount) { calendarListView.stopScroll(); loadMoreEvents(); } }); calendarListView.addOnScrollListener(new RecyclerView.OnScrollListener() { private Calendar savedDate; @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); //scrolled by scroll to position if (dy == 0) { return; } if (!calendarListView.canScrollVertically(-1)) { loadBefore(); } if (getFirstVisibleItemPosition() > listItemModels.size()) return; final ListItemModel firstVisibleItem = listItemModels.get(getFirstVisibleItemPosition()); if (savedDate == null) { savedDate = firstVisibleItem.getCalendar(); return; } if (dy > 0) { //month changed if (firstVisibleItem.getType() == ListItemModel.MONTH && !firstVisibleItem.getCalendar().equals(savedDate)) { savedDate = firstVisibleItem.getCalendar(); if (monthChangeListener != null) monthChangeListener.onMonthChanged(savedDate); } } else { //month changed if (savedDate != null && firstVisibleItem.getCalendar().get(Calendar.MONTH) != savedDate.get(Calendar.MONTH)) { savedDate = firstVisibleItem.getCalendar(); if (monthChangeListener != null) monthChangeListener.onMonthChanged(savedDate); } } //day changed if (firstVisibleItem.getType() == ListItemModel.EVENT && firstVisibleItem.getCalendar().get(Calendar.DATE) != savedDate.get(Calendar.DATE)) { savedDate = firstVisibleItem.getCalendar(); if (dayChangeListener != null) dayChangeListener.onDayChanged(savedDate); } } }); } public void setOnEventClickListener(OnEventClickListener onEventClickListener) { this.onEventClickListener = onEventClickListener; } private void sortListItemsAscending(List<ListItemModel> eventList) { Collections.sort(eventList, getComparator()); } private Comparator<ListItemModel> getComparator() { return new Comparator<ListItemModel>() { @Override public int compare(ListItemModel lhs, ListItemModel rhs) { return lhs.compareTo(rhs); } }; } }