/*
* Calendula - An assistant for personal medication management.
* Copyright (C) 2016 CITIUS - USC
*
* Calendula 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 software. If not, see <http://www.gnu.org/licenses/>.
*/
package es.usc.citius.servando.calendula.fragments;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.app.Fragment;
import android.support.v4.util.Pair;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.iconics.IconicsDrawable;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.LocalDate;
import org.joda.time.LocalTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import es.usc.citius.servando.calendula.DailyAgendaRecyclerAdapter;
import es.usc.citius.servando.calendula.R;
import es.usc.citius.servando.calendula.activities.ConfirmActivity;
import es.usc.citius.servando.calendula.database.DB;
import es.usc.citius.servando.calendula.persistence.DailyScheduleItem;
import es.usc.citius.servando.calendula.persistence.Medicine;
import es.usc.citius.servando.calendula.persistence.Routine;
import es.usc.citius.servando.calendula.persistence.Schedule;
import es.usc.citius.servando.calendula.persistence.ScheduleItem;
import es.usc.citius.servando.calendula.util.DailyAgendaItemStub;
import es.usc.citius.servando.calendula.util.DailyAgendaItemStub.DailyAgendaItemStubElement;
/**
* Daily agenda fragment
*/
public class DailyAgendaFragment extends Fragment{
final String TAG = "DailyAgendaFragment";
View emptyView;
LinearLayoutManager llm;
RecyclerView rv;
DailyAgendaRecyclerAdapter rvAdapter;
DailyAgendaRecyclerAdapter.EventListener rvListener;
List<DailyAgendaItemStub> items = new ArrayList<>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
items = new ArrayList<>();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_daily_agenda, container, false);
rv = (RecyclerView) rootView.findViewById(R.id.rv);
emptyView = rootView.findViewById(R.id.empty_view_placeholder);
setupRecyclerView();
setupEmptyView();
return rootView;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
notifyDataChange();
}
private void setupRecyclerView() {
llm = new LinearLayoutManager(getContext());
rv.setLayoutManager(llm);
rvAdapter = new DailyAgendaRecyclerAdapter(items, rv, llm, getActivity());
rv.setAdapter(rvAdapter);
rv.setItemAnimator(new DefaultItemAnimator());
rvListener = new DailyAgendaRecyclerAdapter.EventListener() {
DateTime firstTime = null;
@Override
public void onItemClick(View v, DailyAgendaItemStub item, int position) {
showConfirmActivity(v, item, position);
}
@Override
public void onBeforeToggleCollapse(boolean expanded, boolean somethingVisible) {
int firstPosition = llm.findFirstVisibleItemPosition();
firstTime = firstPosition >= 0 && firstPosition < items.size() ? items.get(firstPosition).dateTime() : null;
Log.d(TAG, "OnBeforeCollapse, somethingVisible is " + somethingVisible);
if(expanded) {
showOrHideEmptyView(false);
} else if(!expanded && somethingVisible){
showOrHideEmptyView(false);
}else{
showOrHideEmptyView(true);
}
}
@Override
public void onAfterToggleCollapse(boolean expanded, boolean somethingVisible) {
if(expanded && firstTime != null){
scrollTo(firstTime);
}
firstTime = null;
}
};
rvAdapter.setListener(rvListener);
}
private void setupEmptyView() {
Drawable icon = new IconicsDrawable(getContext())
.icon(GoogleMaterial.Icon.gmd_alarm_check)
.colorRes(R.color.agenda_item_title)
.sizeDp(90)
.paddingDp(0);
((ImageView) emptyView.findViewById(R.id.imageView_ok)).setImageDrawable(icon);
}
private void showConfirmActivity(View view, DailyAgendaItemStub item, int position) {
Intent i = new Intent(getContext(), ConfirmActivity.class);
i.putExtra("position", position);
i.putExtra("date", item.date.toString("dd/MM/YYYY"));
if(item.isRoutine) {
i.putExtra("routine_id", item.id);
}else{
i.putExtra("schedule_id", item.id);
i.putExtra("schedule_time", item.time.toString("kk:mm"));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
View v1 = view.findViewById(R.id.patient_avatar);
View v2 = view.findViewById(R.id.linearLayout);
View v3 = view.findViewById(R.id.routines_list_item_name);
if(v1!=null && v2!=null && v3!=null) {
ActivityOptionsCompat activityOptions = ActivityOptionsCompat.makeSceneTransitionAnimation(
getActivity(),
new Pair<>(v1, "avatar_transition"),
new Pair<>(v2, "time"),
new Pair<>(v3, "title")
);
ActivityCompat.startActivity(getActivity(), i, activityOptions.toBundle());
}else{
startActivity(i);
}
} else {
startActivity(i);
}
}
public List<DailyAgendaItemStub> buildItems() {
List<DailyAgendaItemStub> stubs = new ArrayList<>();
final List<DailyScheduleItem> daily = DB.dailyScheduleItems().findAll();
DateTime max = DateTime.now().withTimeAtStartOfDay();
DateTime min = DateTime.now().withTimeAtStartOfDay();
// create stubs for hourly schedule items
for(DailyScheduleItem dailyScheduleItem : daily){
if(dailyScheduleItem.boundToSchedule())
{
Schedule schedule = dailyScheduleItem.schedule();
Medicine medicine = schedule.medicine();
LocalTime time = dailyScheduleItem.time();
LocalDate date = dailyScheduleItem.date();
// create a stub for this item
DailyAgendaItemStub stub = new DailyAgendaItemStub(date,time);
stub.isRoutine = false;
stub.meds = new ArrayList<>();
stub.hasEvents = true;
stub.id = schedule.getId();
stub.patient = schedule.patient();
stub.isRoutine = false;
stub.title = schedule.toReadableString(getContext());
stub.time = time;
// create a element for the schedule item
DailyAgendaItemStubElement el = new DailyAgendaItemStubElement();
el.medName = medicine.name();
el.dose = schedule.dose();
el.displayDose = schedule.displayDose();
el.res = medicine.presentation().getDrawable();
el.presentation = medicine.presentation();
el.minute = time.toString("mm");
el.taken = dailyScheduleItem.takenToday();
stub.meds.add(el);
stubs.add(stub);
DateTime candidate = stub.date.toDateTime(stub.time);
if(candidate.isAfter(max)){
max = candidate;
}
if(candidate.isBefore(min)){
min = candidate;
}
}
}
final Map<LocalDate, Map<Routine, DailyAgendaItemStub>> dateStubs = new HashMap<>();
// create stubs for routine items
for (Routine routine: DB.routines().findAll()) {
for (ScheduleItem scheduleItem : routine.scheduleItems()) {
// get item from daily agenda if exists
List<DailyScheduleItem> dailyScheduleItems = DB.dailyScheduleItems().findAllByScheduleItem(scheduleItem);
for (DailyScheduleItem dailyScheduleItem : dailyScheduleItems) {
// break if not, this means is not enabled for today
if (dailyScheduleItem == null) {
break;
}
LocalDate date = dailyScheduleItem.date();
if (!dateStubs.containsKey(date)) {
dateStubs.put(date, new HashMap<Routine, DailyAgendaItemStub>());
}
Map<Routine, DailyAgendaItemStub> routineStubs = dateStubs.get(date);
DailyAgendaItemStub stub;
LocalTime time = routine.time();
if (!routineStubs.containsKey(routine)) {
// create a new stub and add it to the list
stub = new DailyAgendaItemStub(date, routine.time());
stub.isRoutine = true;
stub.id = routine.getId();
stub.patient = routine.patient();
stub.title = routine.name();
stub.time = time;
stub.meds = new ArrayList<>();
stub.hasEvents = true;
routineStubs.put(routine, stub);
DateTime candidate = stub.date.toDateTime(stub.time);
if (candidate.isAfter(max)) {
max = candidate;
}
if(candidate.isBefore(min)){
min = candidate;
}
} else {
stub = routineStubs.get(routine);
}
Schedule schedule = scheduleItem.schedule();
Medicine medicine = schedule.medicine();
DailyAgendaItemStubElement el = new DailyAgendaItemStubElement();
// add element properties
el.medName = medicine.name();
el.dose = scheduleItem.dose();
el.scheduleItemId = scheduleItem.getId();
el.displayDose = scheduleItem.displayDose();
el.res = medicine.presentation().getDrawable();
el.presentation = medicine.presentation();
el.minute = time.toString("mm");
el.taken = dailyScheduleItem.takenToday();
stub.meds.add(el);
}
}
}
for(LocalDate date : dateStubs.keySet()){
Map<Routine, DailyAgendaItemStub> routineStubs = dateStubs.get(date);
for(Routine r : routineStubs.keySet()){
stubs.add(routineStubs.get(r));
}
}
addEmptyHours(stubs, DateTime.now().minusDays(1), max);
Collections.sort(stubs, DailyAgendaItemStubComparator.instance);
return stubs;
}
public void addEmptyHours(List<DailyAgendaItemStub> stubs, DateTime min, DateTime max){
min = min.withTimeAtStartOfDay();
max = max.withTimeAtStartOfDay().plusDays(1); // end of the day
// add empty hours if there is not an item with the same hour
for(DateTime start = min; start.isBefore(max); start = start.plusHours(1)){
boolean exact = false;
for (DailyAgendaItemStub item : items) {
if(start.equals(item.dateTime())){
exact = true; break;
}
}
Interval hour = new Interval(start, start.plusHours(1));
if(!exact || hour.contains(DateTime.now())) {
stubs.add(new DailyAgendaItemStub(start.toLocalDate(), start.toLocalTime()));
}
if(start.getHourOfDay() == 0){
DailyAgendaItemStub spacer = new DailyAgendaItemStub(start.toLocalDate(), start.toLocalTime());
spacer.isSpacer = true;
stubs.add(spacer);
}
}
}
public void showOrHideEmptyView(boolean show) {
if(show) {
emptyView.setVisibility(View.VISIBLE);
//emptyView.animate().alpha(1);
}
else {
emptyView.setVisibility(View.GONE);
// emptyView.animate().alpha(0).setListener(new AnimatorListenerAdapter() {
// @Override
// public void onAnimationEnd(Animator animation) {
//
// }
// });
}
}
public void toggleViewMode() {
rvAdapter.toggleCollapseMode();
}
public void refresh() {
rvAdapter.notifyDataSetChanged();
}
public void refreshPosition(int position) {
if(position == -1){
notifyDataChange();
return;
}else if(position >= 0 && position < items.size()) {
rvAdapter.updatePosition(position);
}
}
public void scrollTo(DateTime time){
int position = 0;
for(DailyAgendaItemStub stub : items){
if( stub.dateTime().isAfter(time)){
break;
}
position++;
}
if(position > 0)
llm.scrollToPositionWithOffset(position-1, 50);
}
public boolean isExpanded() {
return rvAdapter.isExpanded();
}
public void notifyDataChange() {
try {
Log.d(TAG, "AgendaView NotifyDataChange");
items.clear();
items.addAll(buildItems());
Log.d(TAG, "Items after rebuild " + items.size());
rvAdapter.notifyDataSetChanged();
// show empty list view if there are no items
rv.postDelayed(new Runnable() {
@Override
public void run() {
showOrHideEmptyView(!rvAdapter.isShowingSomething());
}
}, 100);
}catch (Exception e){
Log.e(TAG, "Error onPostExecute", e);
}
}
public void onUserUpdate() {
notifyDataChange();
}
private static class DailyAgendaItemStubComparator implements Comparator<DailyAgendaItemStub> {
static final DailyAgendaItemStubComparator instance = new DailyAgendaItemStubComparator();
private DailyAgendaItemStubComparator(){}
@Override
public int compare(DailyAgendaItemStub a, DailyAgendaItemStub b) {
DateTime aT = a.date.toDateTime(a.time);
DateTime bT = b.date.toDateTime(b.time);
if(aT.compareTo(bT) == 0 && a.isSpacer) {
return -1;
} else if(aT.compareTo(bT) == 0 && b.isSpacer){
return 1;
}else if(aT.compareTo(bT) == 0){
return a.hasEvents ? -1 : 1;
}
return aT.compareTo(bT);
}
}
}