package io.github.memfis19.cadar.internal.ui.month.adapter; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.support.annotation.LayoutRes; import android.support.v4.view.PagerAdapter; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import java.util.ArrayList; import java.util.Calendar; import java.util.HashSet; import java.util.List; import java.util.Set; import io.github.memfis19.cadar.R; 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.internal.process.EventsProcessor; import io.github.memfis19.cadar.internal.process.EventsProcessorCallback; import io.github.memfis19.cadar.internal.ui.month.adapter.decorator.factory.MonthDayDecoratorFactory; import io.github.memfis19.cadar.internal.utils.DateUtils; import io.github.memfis19.cadar.settings.MonthCalendarConfiguration; /** * Created by memfis on 7/19/16. */ public class MonthAdapter extends PagerAdapter implements OnDayChangeListener, EventsProcessorCallback<Calendar, SparseArray<List<Event>>>, MonthHandlerThread.AdapterPrepareListener { private static final String TAG = "MonthAdapter"; private static int CAPACITY = 11; private static int CAPACITY_STEP = 11; private Context context; private LayoutInflater inflater; private MonthCalendarConfiguration monthCalendarConfiguration; private MonthHandlerThread monthHandlerThread; private Handler backgroundHandler; private Handler uiHandler; private Set<Event> events = new HashSet<>(); private List<Event> eventList = new ArrayList<>(); private EventsProcessor<Calendar, SparseArray<List<Event>>> eventsAsyncProcessor; private OnDayChangeListener onDateChangeListener; private DisplayEventCallback<Calendar> callback; private MonthGridCallback monthGridCallback; public interface MonthGridCallback { void onMonthGridReady(Calendar month); } @LayoutRes private int monthDayLayoutId; private MonthDayDecoratorFactory monthDayDecoratorFactory; private List<RecyclerView> monthFragments = new ArrayList<>(); private Calendar initialDate = DateUtils.getCalendarInstance(); private static int INITIAL_POSITION = 0; public MonthAdapter(Context context, MonthHandlerThread monthHandlerThread, EventsProcessor<Calendar, SparseArray<List<Event>>> eventsAsyncProcessor, MonthCalendarConfiguration monthCalendarConfiguration) { this(context, eventsAsyncProcessor, monthCalendarConfiguration.getMonthLayoutId(), monthCalendarConfiguration.getMonthDayDecoratorFactory()); this.monthCalendarConfiguration = monthCalendarConfiguration; Calendar calendar = DateUtils.getCalendarInstance(); calendar = DateUtils.setTimeToMidnight(calendar); Calendar startPeriod = DateUtils.setTimeToMonthStart((Calendar) calendar.clone()); startPeriod.set(monthCalendarConfiguration.getPeriodType(), calendar.get(monthCalendarConfiguration.getPeriodType()) - monthCalendarConfiguration.getPeriodValue()); Calendar endPeriod = DateUtils.setTimeToMonthStart((Calendar) calendar.clone()); endPeriod.set(monthCalendarConfiguration.getPeriodType(), calendar.get(monthCalendarConfiguration.getPeriodType()) + monthCalendarConfiguration.getPeriodValue()); CAPACITY = DateUtils.monthBetweenPure(startPeriod.getTime(), endPeriod.getTime()); CAPACITY_STEP = CAPACITY; this.initialDate = monthCalendarConfiguration.getInitialDay(); this.monthHandlerThread = monthHandlerThread; this.monthHandlerThread.setAdapterPrepareListener(this); backgroundHandler = new Handler(monthHandlerThread.getLooper()); uiHandler = new Handler(Looper.getMainLooper()); } MonthAdapter(Context context, EventsProcessor<Calendar, SparseArray<List<Event>>> eventsAsyncProcessor, @LayoutRes int monthDayLayoutId, MonthDayDecoratorFactory monthDayDecoratorFactory) { this.context = context; inflater = LayoutInflater.from(context); this.eventsAsyncProcessor = eventsAsyncProcessor; this.monthDayLayoutId = monthDayLayoutId; this.monthDayDecoratorFactory = monthDayDecoratorFactory; this.eventsAsyncProcessor.setEventsProcessorCallback(this); } public void setMonthGridCallback(MonthGridCallback monthGridCallback) { this.monthGridCallback = monthGridCallback; } public void setOnDateChangeListener(OnDayChangeListener onDateChangeListener) { this.onDateChangeListener = onDateChangeListener; } public static int getInitialPosition(Calendar initialDate) { if (INITIAL_POSITION == 0) { INITIAL_POSITION = CAPACITY / 2 + 1; } return INITIAL_POSITION; } public Calendar getDayForPosition(int position) { Calendar month = (Calendar) initialDate.clone(); int shiftValue = position - getInitialPosition(initialDate); month.add(Calendar.MONTH, shiftValue); return month; } @Override public int getItemPosition(Object object) { return getInitialPosition(initialDate) + (Integer) ((RecyclerView) object).getTag(); // return getDayPosition(((MonthGridAdapter) ((RecyclerView) object).getAdapter()).getMonth()); } @Override public int getCount() { return CAPACITY; } @Override public boolean isViewFromObject(View view, Object object) { return view.equals(object); } @Override public Object instantiateItem(ViewGroup collection, int position) { RecyclerView recyclerView = (RecyclerView) inflater.inflate(R.layout.month_grid_layout, collection, false); monthFragments.add(recyclerView); int shiftValue = position - getInitialPosition(initialDate); recyclerView.setTag(shiftValue); monthHandlerThread.queuePreparation(initialDate, shiftValue, recyclerView); collection.addView(recyclerView); return recyclerView; } public int getCountOfInstantiateItems() { return monthFragments.size(); } @Override public void destroyItem(ViewGroup collection, int position, Object view) { monthFragments.remove(view); collection.removeView((View) view); } public void refresh() { this.callback = null; displayEvents(); } public void displayEvents(List<Event> eventList, DisplayEventCallback<Calendar> callback) { events.clear(); events.addAll(eventList); this.eventList.clear(); this.eventList.addAll(events); this.callback = callback; eventsAsyncProcessor.setEvents(this.eventList); displayEvents(); } public void addEvents(List<Event> eventList) { events.addAll(eventList); this.eventList.clear(); this.eventList.addAll(events); eventsAsyncProcessor.setEvents(this.eventList); displayEvents(); } public void displayEvents() { for (RecyclerView recyclerView : monthFragments) { if (recyclerView.getAdapter() != null) ((MonthGridAdapter) recyclerView.getAdapter()).requestMonthEvents(); } } public void notifyDayWasSelected() { for (RecyclerView recyclerView : monthFragments) { if (recyclerView.getAdapter() != null) ((MonthGridAdapter) recyclerView.getAdapter()).notifyMonthSetChanged(); } } public int getDayPosition(Calendar day) { int shiftValue = DateUtils.monthBetweenPure(initialDate.getTime(), day.getTime()); return getInitialPosition(initialDate) + shiftValue; } @Override public void onDayChanged(Calendar calendar) { notifyDayWasSelected(); if (onDateChangeListener != null) onDateChangeListener.onDayChanged(calendar); } @Override public void onEventsProcessed(Calendar calendar, SparseArray<List<Event>> calendarEvents) { for (RecyclerView recyclerView : monthFragments) { if (recyclerView.getAdapter() != null) if (((MonthGridAdapter) recyclerView.getAdapter()).getMonth().equals(calendar)) { ((MonthGridAdapter) recyclerView.getAdapter()).displayEventsForMonth(calendarEvents); if (callback != null) callback.onEventsDisplayed(calendar); } } } public final static int FORWARD = 1; public final static int BACKWARD = 2; public void increaseSize(int direction) { if (direction == FORWARD) { CAPACITY += CAPACITY_STEP; notifyDataSetChanged(); } else if (direction == BACKWARD) { CAPACITY += CAPACITY_STEP; INITIAL_POSITION += CAPACITY_STEP; notifyDataSetChanged(); } } @Override public void onReadyAdapter(Calendar month, List<Calendar> monthDays, RecyclerView recyclerView) { MonthGridAdapter monthGridAdapter = new MonthGridAdapter(month, monthDays, monthCalendarConfiguration.isDisplayDaysOutOfMonth()); monthGridAdapter.setOnDateChangeListener(this); monthGridAdapter.setEventsAsyncProcessor(eventsAsyncProcessor); monthGridAdapter.setMonthDayLayoutId(monthDayLayoutId); monthGridAdapter.setMonthDayDecoratorFactory(monthDayDecoratorFactory); CalendarLayoutManager gridLayoutManager = new CalendarLayoutManager(context, 7); recyclerView.setLayoutManager(gridLayoutManager); recyclerView.setAdapter(monthGridAdapter); if (monthGridCallback != null) monthGridCallback.onMonthGridReady(month); // monthGridAdapter.requestDisplayEvents(); } private class CalendarLayoutManager extends GridLayoutManager { public CalendarLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public CalendarLayoutManager(Context context, int spanCount) { super(context, spanCount); } public CalendarLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) { super(context, spanCount, orientation, reverseLayout); } @Override public boolean supportsPredictiveItemAnimations() { return false; } /*** * Workaround for know RV issue * @link https://code.google.com/p/android/issues/detail?id=77846#c10 */ @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { try { super.onLayoutChildren(recycler, state); } catch (IndexOutOfBoundsException e) { Log.e(MonthAdapter.TAG, "IndexOutOfBoundsException in RecyclerView happens"); } } } }