package io.github.memfis19.cadar.view;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import io.github.memfis19.cadar.CalendarController;
import io.github.memfis19.cadar.R;
import io.github.memfis19.cadar.data.entity.Event;
import io.github.memfis19.cadar.event.CalendarPrepareCallback;
import io.github.memfis19.cadar.event.DisplayEventCallback;
import io.github.memfis19.cadar.event.OnDayChangeListener;
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.MonthEventsProcessor;
import io.github.memfis19.cadar.internal.ui.month.MonthCalendarHelper;
import io.github.memfis19.cadar.internal.ui.month.adapter.MonthAdapter;
import io.github.memfis19.cadar.internal.ui.month.adapter.MonthHandlerThread;
import io.github.memfis19.cadar.internal.utils.DateUtils;
import io.github.memfis19.cadar.settings.MonthCalendarConfiguration;
/**
* Created by memfis on 7/14/16.
*/
public class MonthCalendar extends LinearLayout implements ViewPager.OnPageChangeListener,
ScrollManager.OnScrollChanged, CalendarController<MonthCalendarConfiguration, Calendar>, ViewTreeObserver.OnPreDrawListener {
private static final String TAG = "MonthView3";
private Calendar currentMonth;
private int currentPosition = 0;
private MonthHandlerThread monthHandlerThread;
private EventsProcessor<Calendar, SparseArray<List<Event>>> eventsAsyncProcessor;
private LayoutInflater layoutInflater;
private ViewGroup monthHeaderView;
private ViewPager monthGridView;
private MonthAdapter monthAdapter;
private MonthCalendarConfiguration monthCalendarConfiguration;
private SimpleDateFormat weekDayFormatter;
private Calendar workingCalendar = DateUtils.getCalendarInstance();
private OnDayChangeListener onDateChangeListener;
private OnMonthChangeListener onMonthChangeListener;
private CalendarPrepareCallback calendarPrepareCallback;
protected HandlerThread backgroundThread;
protected Handler backgroundHandler;
protected Handler uiHandler = new Handler(Looper.getMainLooper());
private int amountOfReadyMonths = 0;
public void setCalendarPrepareCallback(CalendarPrepareCallback calendarPrepareCallback) {
this.calendarPrepareCallback = calendarPrepareCallback;
}
public MonthCalendar(Context context) {
super(context);
init();
}
public MonthCalendar(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MonthCalendar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs);
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public MonthCalendar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
setOrientation(VERTICAL);
layoutInflater = LayoutInflater.from(getContext());
inflate(getContext(), R.layout.month_calendar_layout, this);
monthHeaderView = (ViewGroup) findViewById(R.id.month_header);
monthGridView = (ViewPager) findViewById(R.id.month_grid);
}
@Override
public void prepareCalendar(final MonthCalendarConfiguration monthCalendarConfiguration) {
this.monthCalendarConfiguration = monthCalendarConfiguration;
backgroundThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
monthHandlerThread = new MonthHandlerThread();
monthHandlerThread.start();
monthHandlerThread.getLooper();
if (monthCalendarConfiguration.getEventsProcessor() != null)
eventsAsyncProcessor = monthCalendarConfiguration.getEventsProcessor();
else
eventsAsyncProcessor = new MonthEventsProcessor(monthCalendarConfiguration.isEventProcessingEnabled(), monthCalendarConfiguration.getEventCalculator());
eventsAsyncProcessor.setEventProcessor(monthCalendarConfiguration.getEventCalculator());
eventsAsyncProcessor.setScrollManager(ScrollManager.geViewInstance(this));
eventsAsyncProcessor.start();
eventsAsyncProcessor.getLooper();
backgroundHandler.post(new Runnable() {
@Override
public void run() {
if (!monthCalendarConfiguration.isWeekDayTitleTranslationEnabled())
weekDayFormatter = new SimpleDateFormat("EEE", monthCalendarConfiguration.getLocale());
int pos = monthCalendarConfiguration.getFirstDayOfWeek();
for (int i = pos; i < pos + 7; ++i) {
initHeaderItem(monthHeaderView, i);
}
monthAdapter = new MonthAdapter(getContext(), monthHandlerThread,
eventsAsyncProcessor, monthCalendarConfiguration);
monthAdapter.setOnDateChangeListener(onDateChangeListener);
monthAdapter.setMonthGridCallback(new MonthAdapter.MonthGridCallback() {
@Override
public void onMonthGridReady(Calendar month) {
amountOfReadyMonths++;
if (amountOfReadyMonths == monthAdapter.getCountOfInstantiateItems() && calendarPrepareCallback != null) {
calendarPrepareCallback.onCalendarReady(MonthCalendar.this);
monthAdapter.setMonthGridCallback(null);
}
}
});
uiHandler.post(new Runnable() {
@Override
public void run() {
monthGridView.setAdapter(monthAdapter);
monthGridView.setOffscreenPageLimit(1);
monthGridView.setCurrentItem(MonthAdapter.getInitialPosition(monthCalendarConfiguration.getInitialDay()), false);
ScrollManager.geViewInstance(MonthCalendar.this).subscribeForScrollStateChanged(MonthCalendar.this);
monthGridView.addOnPageChangeListener(MonthCalendar.this);
getViewTreeObserver().addOnPreDrawListener(MonthCalendar.this);
}
});
}
});
}
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
@Override
public void releaseCalendar() {
monthHandlerThread.quitSafely();
eventsAsyncProcessor.quitSafely();
ScrollManager.geViewInstance(this).releaseScrollManager();
MonthCalendarHelper.updateSelectedDay(MonthCalendarHelper.getToday());
}
public void setOnDayChangeListener(OnDayChangeListener onDateChangeListener) {
this.onDateChangeListener = onDateChangeListener;
if (monthAdapter != null) monthAdapter.setOnDateChangeListener(onDateChangeListener);
}
public void setOnMonthChangeListener(OnMonthChangeListener onMonthChangeListener) {
this.onMonthChangeListener = onMonthChangeListener;
}
@Override
public void setSelectedDay(Calendar selectedDay, boolean scrollToSelectedDay) {
MonthCalendarHelper.updateSelectedDay(selectedDay);
if (scrollToSelectedDay) {
monthGridView.setCurrentItem(monthAdapter.getDayPosition(selectedDay), false);
}
ScrollManager.geViewInstance(this).unSubscribeForScrollStateChanged(this);
monthAdapter.notifyDayWasSelected();
ScrollManager.geViewInstance(this).subscribeForScrollStateChanged(this);
}
private void initHeaderItem(final ViewGroup headerItem, int position) {
int correctPosition = (position > 7) ? (position % 7) : position;
View headerView = null;
switch (correctPosition) {
case Calendar.SUNDAY:
headerView = createDayOfWeekHeader(
Calendar.SUNDAY, headerItem);
break;
case Calendar.MONDAY:
headerView = createDayOfWeekHeader(
Calendar.MONDAY, headerItem);
break;
case Calendar.TUESDAY:
headerView = createDayOfWeekHeader(
Calendar.TUESDAY, headerItem);
break;
case Calendar.WEDNESDAY:
headerView = createDayOfWeekHeader(
Calendar.WEDNESDAY, headerItem);
break;
case Calendar.THURSDAY:
headerView = createDayOfWeekHeader(
Calendar.THURSDAY, headerItem);
break;
case Calendar.FRIDAY:
headerView = createDayOfWeekHeader(
Calendar.FRIDAY, headerItem);
break;
case Calendar.SATURDAY:
headerView = createDayOfWeekHeader(
Calendar.SATURDAY, headerItem);
break;
default:
break;
}
if (headerView != null) {
final View view = headerView;
uiHandler.post(new Runnable() {
@Override
public void run() {
headerItem.addView(view);
}
});
}
}
private View createDayOfWeekHeader(int calendarWeekDay, ViewGroup parent) {
View weekDayView = layoutInflater.inflate(monthCalendarConfiguration.getWeekTitleLayoutId(), parent, false);
String dayOfWeekTitle = "";
if (monthCalendarConfiguration.isWeekDayTitleTranslationEnabled()) {
if (calendarWeekDay == Calendar.MONDAY)
dayOfWeekTitle = getContext().getString(monthCalendarConfiguration.getMondayTitle());
else if (calendarWeekDay == Calendar.TUESDAY)
dayOfWeekTitle = getContext().getString(monthCalendarConfiguration.getTuesdayTitle());
else if (calendarWeekDay == Calendar.WEDNESDAY)
dayOfWeekTitle = getContext().getString(monthCalendarConfiguration.getWednesdayTitle());
else if (calendarWeekDay == Calendar.THURSDAY)
dayOfWeekTitle = getContext().getString(monthCalendarConfiguration.getThursdayTitle());
else if (calendarWeekDay == Calendar.FRIDAY)
dayOfWeekTitle = getContext().getString(monthCalendarConfiguration.getFridayTitle());
else if (calendarWeekDay == Calendar.SATURDAY)
dayOfWeekTitle = getContext().getString(monthCalendarConfiguration.getSaturdayTitle());
else if (calendarWeekDay == Calendar.SUNDAY)
dayOfWeekTitle = getContext().getString(monthCalendarConfiguration.getSundayTitle());
} else {
workingCalendar.set(Calendar.DAY_OF_WEEK, calendarWeekDay);
dayOfWeekTitle = weekDayFormatter.format(workingCalendar.getTime());
}
if (monthCalendarConfiguration.getWeekDayDecorator() != null) {
monthCalendarConfiguration.getWeekDayDecorator().onBindWeekDayView(weekDayView, calendarWeekDay, dayOfWeekTitle);
} else {
((TextView) weekDayView).setGravity(Gravity.CENTER);
((TextView) weekDayView).setText(dayOfWeekTitle);
}
return weekDayView;
}
public void refresh() {
monthAdapter.refresh();
}
@Override
public void displayEvents(List<Event> eventList, DisplayEventCallback<Calendar> callback) {
if (monthAdapter != null)
monthAdapter.displayEvents(eventList, callback);
}
public void addEvents(List<Event> eventList) {
if (monthAdapter != null)
monthAdapter.addEvents(eventList);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
this.currentPosition = position;
if (currentPosition == 0) {
monthAdapter.increaseSize(MonthAdapter.BACKWARD);
} else if (currentPosition == monthAdapter.getCount() - 1) {
monthAdapter.increaseSize(MonthAdapter.FORWARD);
}
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_DRAGGING) {
ScrollManager.geViewInstance(this).notifyScrollStateChanged(ScrollManager.SCROLL_STATE_DRAGGING);
} else if (state == ViewPager.SCROLL_STATE_SETTLING) {
ScrollManager.geViewInstance(this).notifyScrollStateChanged(ScrollManager.SCROLL_STATE_SETTLING);
} else if (state == ViewPager.SCROLL_STATE_IDLE) {
ScrollManager.geViewInstance(this).notifyScrollStateChanged(ScrollManager.SCROLL_STATE_IDLE);
checkMonth();
}
}
private void checkMonth() {
Calendar selectedMonth = monthAdapter.getDayForPosition(currentPosition);
if (currentMonth != selectedMonth) {
currentMonth = selectedMonth;
if (onMonthChangeListener != null) {
onMonthChangeListener.onMonthChanged(currentMonth);
}
}
}
@Override
public void onScrollStateChanged(@ScrollManager.ScrollState int scrollState) {
if (scrollState == ScrollManager.SCROLL_STATE_IDLE) {
monthAdapter.displayEvents();
}
}
}