Commit d1d071ed authored by Varun Patil's avatar Varun Patil

Add heat map to calendar

parent 811c6f55
...@@ -36,6 +36,7 @@ ext { ...@@ -36,6 +36,7 @@ ext {
sectionedRecyclerViewVersion = '1.2.0' sectionedRecyclerViewVersion = '1.2.0'
lottieVersion = '2.7.0' lottieVersion = '2.7.0'
shortcutBadgerVersion = '1.1.22@aar' shortcutBadgerVersion = '1.1.22@aar'
materialCalendarViewVersion = '2.0.1'
} }
dependencies { dependencies {
...@@ -61,5 +62,6 @@ dependencies { ...@@ -61,5 +62,6 @@ dependencies {
implementation "io.github.luizgrp.sectionedrecyclerviewadapter:sectionedrecyclerviewadapter:${sectionedRecyclerViewVersion}" implementation "io.github.luizgrp.sectionedrecyclerviewadapter:sectionedrecyclerviewadapter:${sectionedRecyclerViewVersion}"
implementation "com.airbnb.android:lottie:$lottieVersion" implementation "com.airbnb.android:lottie:$lottieVersion"
implementation "me.leolin:ShortcutBadger:$shortcutBadgerVersion" implementation "me.leolin:ShortcutBadger:$shortcutBadgerVersion"
implementation "com.github.prolificinteractive:material-calendarview:${materialCalendarViewVersion}"
} }
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'
package app.insti.fragment; package app.insti.fragment;
import android.animation.ArgbEvaluator;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.InsetDrawable;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
...@@ -10,10 +14,16 @@ import android.support.v7.widget.Toolbar; ...@@ -10,10 +14,16 @@ import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CalendarView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.prolificinteractive.materialcalendarview.CalendarDay;
import com.prolificinteractive.materialcalendarview.DayViewDecorator;
import com.prolificinteractive.materialcalendarview.DayViewFacade;
import com.prolificinteractive.materialcalendarview.MaterialCalendarView;
import com.prolificinteractive.materialcalendarview.OnDateSelectedListener;
import com.prolificinteractive.materialcalendarview.OnMonthChangedListener;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.ParseException; import java.text.ParseException;
...@@ -21,7 +31,11 @@ import java.text.SimpleDateFormat; ...@@ -21,7 +31,11 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import app.insti.R; import app.insti.R;
...@@ -45,7 +59,8 @@ public class CalendarFragment extends BaseFragment { ...@@ -45,7 +59,8 @@ public class CalendarFragment extends BaseFragment {
FloatingActionButton fab; FloatingActionButton fab;
private View view; private View view;
private FeedAdapter feedAdapter = null; private FeedAdapter feedAdapter = null;
private List<Event> events; private List<Event> events = new ArrayList<>();
private HashSet<CalendarDay> haveMonths = new HashSet<>();
public CalendarFragment() { public CalendarFragment() {
...@@ -56,58 +71,120 @@ public class CalendarFragment extends BaseFragment { ...@@ -56,58 +71,120 @@ public class CalendarFragment extends BaseFragment {
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
// Inflate the layout for this fragment
// Inflate the layout for this fragment
view = inflater.inflate(R.layout.fragment_calendar, container, false); view = inflater.inflate(R.layout.fragment_calendar, container, false);
fab = (FloatingActionButton) view.findViewById(R.id.fab); fab = (FloatingActionButton) view.findViewById(R.id.fab);
// Setup toolbar
Toolbar toolbar = getActivity().findViewById(R.id.toolbar); Toolbar toolbar = getActivity().findViewById(R.id.toolbar);
toolbar.setTitle("Calendar"); toolbar.setTitle("Calendar");
Utils.setSelectedMenuItem(getActivity(), R.id.nav_calendar); Utils.setSelectedMenuItem(getActivity(), R.id.nav_calendar);
final CalendarView simpleCalendarView = (CalendarView) view.findViewById(R.id.simpleCalendarView); // get the reference of CalendarView // Handle selecting date
simpleCalendarView.setFirstDayOfWeek(1); // set Sunday as the first day of the week final MaterialCalendarView matCalendarView = view.findViewById(R.id.simpleCalendarView);
matCalendarView.setOnDateChangedListener(new OnDateSelectedListener() {
simpleCalendarView.setWeekNumberColor(getResources().getColor(R.color.colorCalendarWeek));//setWeekNumberColor
simpleCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
@Override @Override
public void onSelectedDayChange(CalendarView view, int year, int month, int dayOfMonth) { public void onDateSelected(@NonNull MaterialCalendarView widget, @NonNull CalendarDay date, boolean selected) {
String sdate = dayOfMonth + "/" + (month + 1) + "/" + year; if (selected) {
try { try {
Date showDate = new SimpleDateFormat("dd/M/yyyy").parse(sdate); showEventsForDate(toDate(date));
showEventsForDate(showDate); } catch (ParseException e) {
} catch (ParseException e) { e.printStackTrace();
e.printStackTrace(); }
} }
} }
}); });
fab.setOnClickListener(new View.OnClickListener() {
// Update events on month change
matCalendarView.setOnMonthChangedListener(new OnMonthChangedListener() {
@Override
public void onMonthChanged(MaterialCalendarView widget, CalendarDay date) {
updateEvents(date, false);
}
});
// Handle fab click
fab.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
AddEventFragment addEventFragment = new AddEventFragment(); AddEventFragment addEventFragment = new AddEventFragment();
((MainActivity) getActivity()).updateFragment(addEventFragment); ((MainActivity) getActivity()).updateFragment(addEventFragment);
} }
}); });
// Show the fab if we can make events
if (((MainActivity) getActivity()).createEventAccess()) { if (((MainActivity) getActivity()).createEventAccess()) {
fab.show(); fab.show();
} }
updateEvents();
return view; return view;
}
@Override
public void onStart() {
super.onStart();
updateEvents(CalendarDay.today(), true);
}
/** Convert CalendarDay to Date */
public Date toDate(CalendarDay date) throws ParseException {
String sdate = date.getDay() + "/" + date.getMonth() + "/" + date.getYear();
Date showDate = new SimpleDateFormat("dd/M/yyyy").parse(sdate);
return showDate;
}
/** Decorator for Calendar */
public class EventDecorator implements DayViewDecorator {
private final int color = getResources().getColor(R.color.colorAccent);
private final int white = getResources().getColor(R.color.primaryTextColor);
private final HashSet<CalendarDay> dates;
private final int alpha;
public EventDecorator(int alpha, HashSet<CalendarDay> dates) {
this.dates = dates;
this.alpha = alpha;
}
@Override
public boolean shouldDecorate(CalendarDay day) {
return dates.contains(day);
}
@Override
public void decorate(DayViewFacade view) {
GradientDrawable gD = new GradientDrawable();
gD.setColor((int) new ArgbEvaluator().evaluate(((float) alpha / 255.0f), white, color));
gD.setShape(GradientDrawable.OVAL);
InsetDrawable iD = new InsetDrawable(gD, 15);
view.setBackgroundDrawable(iD);
}
} }
private void updateEvents() { private void updateEvents(CalendarDay calendarDay, final boolean setToday) {
// Do not make duplicate calls
if (!setToday && haveMonths.contains(calendarDay)) return;
haveMonths.add(calendarDay);
// Parsers
String ISO_FORMAT = "yyyy-MM-dd HH:mm:ss"; String ISO_FORMAT = "yyyy-MM-dd HH:mm:ss";
final TimeZone utc = TimeZone.getTimeZone("UTC"); final TimeZone utc = TimeZone.getTimeZone("UTC");
final SimpleDateFormat isoFormatter = new SimpleDateFormat(ISO_FORMAT); final SimpleDateFormat isoFormatter = new SimpleDateFormat(ISO_FORMAT);
isoFormatter.setTimeZone(utc); isoFormatter.setTimeZone(utc);
// Get the date to start at
final Date today = new Date(); final Date today = new Date();
// Get the start date
final Date startDate;
try {
startDate = toDate(calendarDay);
} catch (ParseException ignored) { return; }
// Get start and end times
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
cal.setTime(startDate);
cal.add(Calendar.MONTH, -1); cal.add(Calendar.MONTH, -1);
final Date oneMonthBackDate = cal.getTime(); final Date oneMonthBackDate = cal.getTime();
cal.add(Calendar.MONTH, 2); cal.add(Calendar.MONTH, 2);
...@@ -116,22 +193,42 @@ public class CalendarFragment extends BaseFragment { ...@@ -116,22 +193,42 @@ public class CalendarFragment extends BaseFragment {
final String oneMonthBack = isoFormatter.format(oneMonthBackDate).toString(); final String oneMonthBack = isoFormatter.format(oneMonthBackDate).toString();
final String oneMonthOn = isoFormatter.format(oneMonthOnDate).toString(); final String oneMonthOn = isoFormatter.format(oneMonthOnDate).toString();
// Make the API call
RetrofitInterface retrofitInterface = Utils.getRetrofitInterface(); RetrofitInterface retrofitInterface = Utils.getRetrofitInterface();
retrofitInterface.getEventsBetweenDates(Utils.getSessionIDHeader(), oneMonthBack, oneMonthOn).enqueue(new Callback<NewsFeedResponse>() { retrofitInterface.getEventsBetweenDates(Utils.getSessionIDHeader(), oneMonthBack, oneMonthOn).enqueue(new Callback<NewsFeedResponse>() {
@Override @Override
public void onResponse(Call<NewsFeedResponse> call, Response<NewsFeedResponse> response) { public void onResponse(Call<NewsFeedResponse> call, Response<NewsFeedResponse> response) {
if (response.isSuccessful()) { if (response.isSuccessful()) {
// Concatenate the response
NewsFeedResponse newsFeedResponse = response.body(); NewsFeedResponse newsFeedResponse = response.body();
events = newsFeedResponse.getEvents(); List<Event> eventList = newsFeedResponse.getEvents();
DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy"); if (eventList == null) return;
getView().findViewById(R.id.calendar_layout).setVisibility(VISIBLE);
try { // Concatenate
Date todayWithZeroTime = formatter.parse(formatter.format(today)); for (Event event : eventList) {
showEventsForDate(todayWithZeroTime); if (!events.contains(event)) events.add(event);
} catch (ParseException e) { }
e.printStackTrace();
// Make the calendar visible
getView().findViewById(R.id.calendar_layout).setVisibility(VISIBLE);
getActivity().findViewById(R.id.loadingPanel).setVisibility(View.GONE);
// Initialize to show today's date
if (setToday) {
// Show today
try {
DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
Date todayWithZeroTime = formatter.parse(formatter.format(today));
showEventsForDate(todayWithZeroTime);
} catch (ParseException ignored) {}
// Select today's date
final MaterialCalendarView matCalendarView = view.findViewById(R.id.simpleCalendarView);
matCalendarView.setSelectedDate(CalendarDay.today());
} }
// Generate the decorators
showHeatMap(events);
} }
} }
...@@ -143,6 +240,58 @@ public class CalendarFragment extends BaseFragment { ...@@ -143,6 +240,58 @@ public class CalendarFragment extends BaseFragment {
}); });
} }
/** Build and show the heat map from the list of events */
private void showHeatMap(List<Event> eventList) {
// Build strength map for each date
Map<CalendarDay, Integer> strength = new HashMap<>();
for (Event event : eventList) {
// Get starting date
Calendar calendar = Calendar.getInstance();
calendar.setTime(event.getEventStartTime());
CalendarDay day = CalendarDay.from(
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH) + 1,
calendar.get(Calendar.DATE)
);
// Update the map with strength
if (strength.containsKey(day)) {
strength.put(day, strength.get(day) + 1);
} else {
strength.put(day, 1);
}
}
// Get the calendar
final MaterialCalendarView matCalendarView = view.findViewById(R.id.simpleCalendarView);
// Remove all decorators
matCalendarView.removeDecorators();
// Create decorator for each color type
final int scale = 2;
final int maxMult = 5;
final int alphaStep = (int) (255.0f / (scale * maxMult));
for (int i = 1; i <= maxMult; i++) {
HashSet<CalendarDay> days = new HashSet<>();
// Iterate over the map to check remaining entries
Iterator it = strength.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
int noEvents = (Integer) pair.getValue();
if (noEvents <= i * scale || (i == maxMult && noEvents > i * scale)) {
days.add((CalendarDay) pair.getKey());
it.remove();
}
}
// Add the decorator
if (days.size() > 0)
matCalendarView.addDecorator(new EventDecorator(scale * i * alphaStep, days));
}
}
private void showEventsForDate(Date date) { private void showEventsForDate(Date date) {
/* Skip if we're already destroyed */ /* Skip if we're already destroyed */
if (getActivity() == null || getView() == null) return; if (getActivity() == null || getView() == null) return;
......
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:enterFadeDuration="@android:integer/config_shortAnimTime"
android:exitFadeDuration="@android:integer/config_shortAnimTime">
<item android:state_checked="true"
android:color="@color/secondaryTextColor" />
<item android:state_pressed="true"
android:color="@color/secondaryTextColor" />
<item android:state_enabled="false"
android:color="#BBBBBB" />
<item android:color="@color/secondaryTextColor" />
</selector>
...@@ -21,14 +21,19 @@ ...@@ -21,14 +21,19 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="60dp" android:layout_height="60dp"
android:background="@color/colorPrimary" /> android:background="@color/colorPrimary" />
<CalendarView <com.prolificinteractive.materialcalendarview.MaterialCalendarView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/simpleCalendarView" android:id="@+id/simpleCalendarView"
android:layout_width="fill_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:focusedMonthDateColor="#000000" app:mcv_showOtherDates="all"
android:unfocusedMonthDateColor="#FFFFFF" /> app:mcv_selectionColor="@color/colorPrimary"
app:mcv_headerTextAppearance="@style/MatCalendarHeader"
app:mcv_leftArrow="@color/primaryTextColor"
app:mcv_rightArrow="@color/primaryTextColor"
app:mcv_dateTextAppearance="@style/CalendarDateAppearance"
android:padding="5dp" />
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
......
...@@ -77,4 +77,14 @@ ...@@ -77,4 +77,14 @@
<item name="android:windowEnterAnimation">@android:anim/fade_in</item> <item name="android:windowEnterAnimation">@android:anim/fade_in</item>
<item name="android:windowExitAnimation">@android:anim/fade_out</item> <item name="android:windowExitAnimation">@android:anim/fade_out</item>
</style> </style>
<style name="MatCalendarHeader" parent="TextAppearance.MaterialCalendarWidget.Header">
<item name="android:textColor">@color/primaryTextColor</item>
<item name="android:textStyle">normal</item>
</style>
<style name="CalendarDateAppearance" parent="TextAppearance.AppCompat.Medium">
<item name="android:textColor">@color/mcv_text_date_light</item>
<item name="android:textSize">14sp</item>
</style>
</resources> </resources>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment