/*
* ____.____ __.____ ___ _____
* | | |/ _| | \ / _ \ ______ ______
* | | < | | / / /_\ \\____ \\____ \
* /\__| | | \| | / / | \ |_> > |_> >
* \________|____|__ \______/ \____|__ / __/| __/
* \/ \/|__| |__|
*
* Copyright (c) 2014-2015 Paul "Marunjar" Pretsch
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
package org.voidsink.anewjkuapp.fragment;
import android.accounts.Account;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.UriMatcher;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.graphics.ColorUtils;
import android.text.format.DateUtils;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import com.alamkanak.weekview.DateTimeInterpreter;
import com.alamkanak.weekview.WeekView;
import com.alamkanak.weekview.WeekViewEvent;
import com.alamkanak.weekview.WeekViewLoader;
import org.voidsink.anewjkuapp.R;
import org.voidsink.anewjkuapp.base.BaseContentObserver;
import org.voidsink.anewjkuapp.base.BaseFragment;
import org.voidsink.anewjkuapp.base.ContentObserverListener;
import org.voidsink.anewjkuapp.calendar.CalendarContractWrapper;
import org.voidsink.anewjkuapp.calendar.CalendarUtils;
import org.voidsink.anewjkuapp.update.ImportCalendarTask;
import org.voidsink.anewjkuapp.update.UpdateService;
import org.voidsink.anewjkuapp.utils.AppUtils;
import org.voidsink.anewjkuapp.utils.Consts;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
public class CalendarFragment2 extends BaseFragment implements ContentObserverListener,
WeekView.EventClickListener, WeekView.EventLongPressListener, LoaderManager.LoaderCallbacks<Cursor> {
private static final String TAG = CalendarFragment2.class.getSimpleName();
private static final String ARG_CAL_LOAD_NOW = "CLN";
private static final String ARG_CAL_LOAD_THEN = "CLT";
private BaseContentObserver mDataObserver;
private WeekView mWeekView;
private final MyWeekViewLoader mWeekViewLoader = new MyWeekViewLoader();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_calendar_2, container,
false);
mWeekView = (WeekView) view.findViewById(R.id.weekView);
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MONTH, -1);
mWeekView.setMinDate(cal);
cal = Calendar.getInstance();
cal.add(Calendar.MONTH, 2);
mWeekView.setMaxDate(cal);
mWeekView.setOnEventClickListener(this);
mWeekView.setEventLongPressListener(this);
// Set long press listener for events.
//mWeekView.setEventLongPressListener(this);
// Set formatter for Date/Time
mWeekView.setDateTimeInterpreter(new CalendarDateTimeInterpreter(getContext()));
mWeekViewLoader.setDaysInPeriod(mWeekView.getNumberOfVisibleDays() * 4);
mWeekView.setWeekViewLoader(mWeekViewLoader);
mWeekView.setScrollListener(mWeekViewLoader);
return view;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.calendar, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_refresh_calendar:
Intent mUpdateService = new Intent(getActivity(), UpdateService.class);
mUpdateService.putExtra(Consts.ARG_UPDATE_CAL, true);
getActivity().startService(mUpdateService);
return true;
case R.id.action_cal_goto_today:
goToDate(System.currentTimeMillis());
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onStart() {
super.onStart();
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(CalendarContractWrapper.AUTHORITY(), CalendarContractWrapper.Events.CONTENT_URI().buildUpon().appendPath("#").build().toString(), 0);
mDataObserver = new BaseContentObserver(uriMatcher, this);
// listen to all changes
getActivity().getContentResolver().registerContentObserver(
CalendarContractWrapper.Events.CONTENT_URI().buildUpon()
.appendPath("#").build(), false, mDataObserver);
}
@Override
public void onStop() {
super.onStop();
getActivity().getContentResolver().unregisterContentObserver(
mDataObserver);
mDataObserver = null;
}
@Override
protected String getScreenName() {
return Consts.SCREEN_CALENDAR_2;
}
@Override
public void onEventClick(WeekViewEvent weekViewEvent, RectF rectF) {
AppUtils.showEventLocation(getContext(), weekViewEvent.getLocation());
}
@Override
public void onContentChanged(boolean selfChange) {
loadData(mWeekView.getFirstVisibleDay());
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mWeekView != null) {
Calendar day = mWeekView.getFirstVisibleDay();
if (day != null) {
double hour = mWeekView.getFirstVisibleHour();
// check range
if (hour < 0) hour = 0;
else if (hour >= 23.99) hour = 23.99; //~23:59
int iHour = (int) hour;
int iMinute = (int) ((hour - iHour) * 60);
day.set(Calendar.HOUR_OF_DAY, iHour);
day.set(Calendar.MINUTE, iMinute);
outState.putLong(Consts.ARG_CALENDAR_NOW, day.getTimeInMillis());
Log.d(TAG, String.format("saveDateTime: %d", day.getTimeInMillis()));
}
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
long date = System.currentTimeMillis();
if (savedInstanceState != null && savedInstanceState.containsKey(Consts.ARG_CALENDAR_NOW)) {
date = savedInstanceState.getLong(Consts.ARG_CALENDAR_NOW);
}
goToDate(date);
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date);
loadData(cal);
}
private void loadData(Calendar date) {
if (mWeekViewLoader != null && date != null) {
int periodIndex = (int) mWeekViewLoader.toWeekViewPeriodIndex(date);
mWeekViewLoader.loadPeriod(periodIndex - 1, true);
mWeekViewLoader.loadPeriod(periodIndex, true);
mWeekViewLoader.loadPeriod(periodIndex + 1, true);
}
}
private void goToDate(long time) {
Log.d(TAG, String.format("goToDate: %d", time));
Calendar day = Calendar.getInstance();
day.setTimeInMillis(time);
double hour = day.get(Calendar.HOUR_OF_DAY) + (day.get(Calendar.MINUTE) / 60);
mWeekView.goToDate(day);
mWeekView.goToHour(hour);
}
@Override
public void onEventLongPress(WeekViewEvent event, RectF eventRect) {
AppUtils.showEventInCalendar(getContext(), event.getId(), event.getStartTime().getTimeInMillis());
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Account mAccount = AppUtils.getAccount(getContext());
String calIDLva = CalendarUtils.getCalIDByName(getContext(),
mAccount, CalendarUtils.ARG_CALENDAR_COURSE, true);
String calIDExam = CalendarUtils.getCalIDByName(getContext(),
mAccount, CalendarUtils.ARG_CALENDAR_EXAM, true);
if (calIDLva == null || calIDExam == null) {
Log.w(TAG, "no events loaded, calendars not found");
return null;
}
return new CursorLoader(getContext(), CalendarContractWrapper.Events.CONTENT_URI(),
CalendarUtils.EVENT_PROJECTION,
"("
+ CalendarContractWrapper.Events
.CALENDAR_ID()
+ " = ? or "
+ CalendarContractWrapper.Events
.CALENDAR_ID() + " = ? ) and "
+ CalendarContractWrapper.Events.DTSTART()
+ " >= ? and "
+ CalendarContractWrapper.Events.DTSTART()
+ " <= ? and "
+ CalendarContractWrapper.Events.DELETED()
+ " != 1",
new String[]{calIDExam, calIDLva,
Long.toString(args.getLong(ARG_CAL_LOAD_NOW)), Long.toString(args.getLong(ARG_CAL_LOAD_THEN))},
CalendarContractWrapper.Events.DTSTART() + " ASC");
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
ArrayList<WeekViewEvent> events = mWeekViewLoader.getEvents(loader.getId());
events.clear();
Account mAccount = AppUtils.getAccount(getContext());
if (mAccount != null) {
// fetch calendar colors
final SparseIntArray mColors = new SparseIntArray();
ContentResolver cr = getContext().getContentResolver();
Cursor cursor = cr
.query(CalendarContractWrapper.Calendars.CONTENT_URI(),
new String[]{
CalendarContractWrapper.Calendars._ID(),
CalendarContractWrapper.Calendars
.CALENDAR_COLOR()}, null, null,
null);
if (cursor != null) {
while (cursor.moveToNext()) {
int color = cursor.getInt(1);
double lastContrast = ColorUtils.calculateContrast(color, mWeekView.getEventTextColor());
//Log.d(TAG, String.format("color=%d %d %d, contrast=%f", Color.red(color), Color.green(color), Color.blue(color), lastContrast));
while (lastContrast < 1.6) {
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
hsv[2] = Math.max(0f, hsv[2] - 0.033f); // darken
color = Color.HSVToColor(hsv);
lastContrast = ColorUtils.calculateContrast(color, mWeekView.getEventTextColor());
//Log.d(TAG, String.format("new color=%d %d %d, contrast=%f", Color.red(color), Color.green(color), Color.blue(color), lastContrast));
if (hsv[2] == 0) break;
}
mColors.put(cursor.getInt(0), color);
}
cursor.close();
}
if (data != null) {
data.moveToFirst();
data.moveToPrevious();
while (data.moveToNext()) {
boolean allDay = data.getInt(CalendarUtils.COLUMN_EVENT_ALL_DAY) == 1;
Calendar startTime = Calendar.getInstance();
if (allDay) {
startTime.setTimeZone(TimeZone.getTimeZone("GMT+0"));
}
startTime.setTimeInMillis(data.getLong(CalendarUtils.COLUMN_EVENT_DTSTART));
Calendar endTime = Calendar.getInstance();
if (allDay) {
endTime.setTimeZone(TimeZone.getTimeZone("GMT+0"));
}
endTime.setTimeInMillis(data.getLong(CalendarUtils.COLUMN_EVENT_DTEND));
if (allDay && endTime.getTimeInMillis() % DateUtils.DAY_IN_MILLIS == 0) {
endTime.add(Calendar.MILLISECOND, -1);
}
WeekViewEvent event = new WeekViewEvent(data.getLong(CalendarUtils.COLUMN_EVENT_ID),
data.getString(CalendarUtils.COLUMN_EVENT_TITLE),
data.getString(CalendarUtils.COLUMN_EVENT_LOCATION),
startTime,
endTime,
allDay);
event.setColor(mColors.get(data.getInt(CalendarUtils.COLUMN_EVENT_CAL_ID)));
events.add(event);
}
}
}
mWeekView.notifyDatasetChanged();
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mWeekViewLoader.removeEvents(loader.getId());
}
private class MyWeekViewLoader implements WeekViewLoader, WeekView.ScrollListener {
private int mDaysInPeriod = 7;
private final SparseArray<ArrayList<WeekViewEvent>> mEvents;
private final ArrayList<Integer> mLastLoadedPeriods;
private int mLastPeriodIndex = 0;
public MyWeekViewLoader() {
mLastLoadedPeriods = new ArrayList<>();
mEvents = new SparseArray<>();
}
public void setDaysInPeriod(int daysInPeriod) {
mDaysInPeriod = daysInPeriod;
}
public ArrayList<WeekViewEvent> getEvents(int periodIndex) {
ArrayList<WeekViewEvent> events = mEvents.get(periodIndex);
if (events == null) {
events = new ArrayList<>();
mEvents.put(periodIndex, events);
}
return mEvents.get(periodIndex);
}
public void removeEvents(int periodIndex) {
mEvents.remove(periodIndex);
}
@Override
public double toWeekViewPeriodIndex(Calendar instance) {
Calendar now = Calendar.getInstance();
now.set(Calendar.HOUR_OF_DAY, 0);
now.set(Calendar.MINUTE, 0);
now.set(Calendar.SECOND, 0);
now.set(Calendar.MILLISECOND, 0);
long days = (instance.getTimeInMillis() - now.getTimeInMillis()) / DateUtils.DAY_IN_MILLIS;
int halfDaysInPeriod = mDaysInPeriod / 2;
int periodIndex = 0;
if (days > halfDaysInPeriod) {
periodIndex = 1 + (int) (days - halfDaysInPeriod - 1) / mDaysInPeriod;
} else if (days < -halfDaysInPeriod) {
periodIndex = -1 + (int) (days + halfDaysInPeriod + 1) / mDaysInPeriod;
}
//Log.d(TAG, String.format("%s toWeekViewPeriodIndex %d (%d days)", SimpleDateFormat.getDateTimeInstance().format(instance.getTime()), periodIndex, days));
return periodIndex;
}
@Override
public List<WeekViewEvent> onLoad(int periodIndex) {
return getEvents(periodIndex);
}
public void loadPeriod(final int periodIndex, final boolean forceIt) {
new Runnable() {
@Override
public void run() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
int index = mLastLoadedPeriods.indexOf(periodIndex);
if (index < 0 || forceIt) {
mLastLoadedPeriods.add(0, periodIndex);
int halfDaysInPeriod = mDaysInPeriod / 2;
Calendar now = Calendar.getInstance();
now.add(Calendar.DATE, periodIndex * mDaysInPeriod - halfDaysInPeriod);
now.set(Calendar.HOUR_OF_DAY, 0);
now.set(Calendar.MINUTE, 0);
now.set(Calendar.SECOND, 0);
now.set(Calendar.MILLISECOND, 0);
Calendar then = Calendar.getInstance();
then.add(Calendar.DATE, periodIndex * mDaysInPeriod + halfDaysInPeriod - 1);
then.set(Calendar.HOUR_OF_DAY, 23);
then.set(Calendar.MINUTE, 59);
then.set(Calendar.SECOND, 59);
then.set(Calendar.MILLISECOND, 999);
Bundle args = new Bundle();
args.putLong(ARG_CAL_LOAD_NOW, now.getTimeInMillis());
args.putLong(ARG_CAL_LOAD_THEN, then.getTimeInMillis());
//Log.d(TAG, String.format("loadPeriod %d(%d) %s - %s", periodIndex, index, SimpleDateFormat.getDateTimeInstance().format(now.getTime()), SimpleDateFormat.getDateTimeInstance().format(then.getTime())));
if (index >= 0) {
mLastLoadedPeriods.remove(index);
getLoaderManager().restartLoader(periodIndex, args, CalendarFragment2.this);
} else {
getLoaderManager().initLoader(periodIndex, args, CalendarFragment2.this);
}
while (mLastLoadedPeriods.size() > 3) {
int removed = mLastLoadedPeriods.remove(mLastLoadedPeriods.size() - 1);
//Log.d(TAG, String.format("period removed %d", removed));
getLoaderManager().destroyLoader(removed);
}
}
}
}.run();
}
@Override
public void onFirstVisibleDayChanged(Calendar newFirstVisibleDay, Calendar oldFirstVisibleDay) {
int periodIndex = (int) toWeekViewPeriodIndex(newFirstVisibleDay);
if (oldFirstVisibleDay == null || mLastPeriodIndex != periodIndex) {
mLastPeriodIndex = periodIndex;
loadPeriod(periodIndex - 1, false);
loadPeriod(periodIndex, false);
loadPeriod(periodIndex + 1, false);
}
}
}
private class CalendarDateTimeInterpreter implements DateTimeInterpreter {
final DateFormat mDateFormat;
final DateFormat mTimeFormat;
CalendarDateTimeInterpreter(Context context) {
Locale locale = AppUtils.getLocale(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mDateFormat = new SimpleDateFormat(android.text.format.DateFormat.getBestDateTimePattern(locale, "EEEMMdd"), locale);
} else {
mDateFormat = SimpleDateFormat.getDateInstance(SimpleDateFormat.SHORT, locale);
}
mTimeFormat = android.text.format.DateFormat.getTimeFormat(context);
}
@Override
public String interpretDate(Calendar calendar) {
return mDateFormat.format(calendar.getTime());
}
@Override
public String interpretTime(int hour, int minute) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return mTimeFormat.format(calendar.getTime());
}
}
}