package com.fourtails.usuariolecturista.fragments;
import android.Manifest;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.CardView;
import android.text.format.Time;
import android.transition.TransitionInflater;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.activeandroid.query.Select;
import com.db.chart.Tools;
import com.db.chart.listener.OnEntryClickListener;
import com.db.chart.model.LineSet;
import com.db.chart.view.LineChartView;
import com.db.chart.view.XController;
import com.db.chart.view.YController;
import com.db.chart.view.animation.Animation;
import com.db.chart.view.animation.easing.BaseEasingMethod;
import com.db.chart.view.animation.easing.QuintEase;
import com.db.chart.view.animation.style.DashAnimation;
import com.fourtails.usuariolecturista.MainActivity;
import com.fourtails.usuariolecturista.R;
import com.fourtails.usuariolecturista.camera.CameraScreenActivity;
import com.fourtails.usuariolecturista.model.ChartReading;
import com.fourtails.usuariolecturista.ottoEvents.AndroidBus;
import com.melnykov.fab.FloatingActionButton;
import com.orhanobut.logger.Logger;
import com.squareup.otto.Bus;
import com.squareup.otto.Subscribe;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
import de.keyboardsurfer.android.widget.crouton.Crouton;
import de.keyboardsurfer.android.widget.crouton.Style;
/**
* This is the balance fragment where it shows your metrics for the last reading, this month etc,
* also calls for a facebook publish
*/
public class ReadingsFragment extends Fragment {
public static final String TAG = "ReadingsFragment";
public static final int MY_PERMISSIONS_REQUEST_CAMERA = 123;
public static Bus readingsBus;
/**
* Charts ***********************************************************************************
*/
private final TimeInterpolator enterInterpolator = new DecelerateInterpolator(1.5f);
private final TimeInterpolator exitInterpolator = new AccelerateInterpolator();
private static float mCurrOverlapFactor;
private static int[] mCurrOverlapOrder;
private static float mOldOverlapFactor;
private static int[] mOldOverlapOrder;
/**
* Ease
*/
private static BaseEasingMethod mCurrEasing;
private static BaseEasingMethod mOldEasing;
/**
* Enter
*/
private static float mCurrStartX;
private static float mCurrStartY;
private static float mOldStartX;
private static float mOldStartY;
/**
* Alpha
*/
private static int mCurrAlpha;
private static int mOldAlpha;
/**
* Line
*/
private Paint mLineGridPaint;
private TextView mLineTooltip;
private final OnEntryClickListener lineEntryListener = new OnEntryClickListener() {
@Override
public void onClick(int setIndex, int entryIndex, Rect rect) {
System.out.println(setIndex);
System.out.println(entryIndex);
if (mLineTooltip == null)
showLineTooltip(setIndex, entryIndex, rect);
else
dismissLineTooltip(setIndex, entryIndex, rect);
}
};
private final View.OnClickListener lineClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mLineTooltip != null)
dismissLineTooltip(-1, -1, null);
}
};
private Handler mHandler;
/**
* This will run the update after 50ms, it fires after a dismiss on the chart and will attempt
* the transition
*/
private final Runnable mMakeTransition = new Runnable() {
@Override
public void run() {
mHandler.postDelayed(new Runnable() {
public void run() {
changeGraphClickedAction();
}
}, 50);
}
};
private boolean isAnimationRunning = false;
@Bind(R.id.lineChartReadings)
LineChartView mLineChart;
/**
* Injected views and clickListeners ********************************************************
*/
@Bind(R.id.fabScan)
FloatingActionButton fabScan;
@OnClick(R.id.fabScan)
public void scanButtonClicked() {
checkForCameraPermissions();
}
private void checkForCameraPermissions() {
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(getActivity(),
Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(),
Manifest.permission.CAMERA)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
new AlertDialog.Builder(getActivity())
.setMessage("Necesitamos la camara para tomar una lectura")
.setCancelable(false)
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
ActivityCompat.requestPermissions(getActivity(),
new String[]{Manifest.permission.CAMERA},
MY_PERMISSIONS_REQUEST_CAMERA);
}
})
.setNegativeButton("Cancelar", null)
.show();
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(getActivity(),
new String[]{Manifest.permission.CAMERA},
MY_PERMISSIONS_REQUEST_CAMERA);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
} else {
Intent cameraActivity = new Intent(getActivity(), CameraScreenActivity.class);
MainActivity.bus.post(cameraActivity);
}
}
@Bind(R.id.fabChangeGraph)
FloatingActionButton fabChangeGraph;
@Bind(R.id.cardViewReadings)
CardView linechartCardView;
@Bind(R.id.cardViewReadingsBottom)
CardView sharedCardView;
@Bind(R.id.progressBarReadings)
ProgressBar progressBar;
@Bind(R.id.textViewNoReadingsMsg)
TextView textViewNoReadings;
@Bind(R.id.textViewTotalReadingsForThisPeriod)
TextView textViewTotalLitersForThisPeriod;
@Bind(R.id.textViewLastReadingDate)
TextView textViewLastReadingDate;
@Bind(R.id.textViewButtonInvitationReadings)
TextView textViewButtonInvitationReadings;
private float[] chartValues;
private float[] chartValuesForAnimation;
List<ChartReading> mReadings;
Time time;
@OnClick(R.id.fabChangeGraph)
public void changeGraphClicked() {
if (!isAnimationRunning) {
fabChangeGraph.setEnabled(false);
isAnimationRunning = true;
hideChartThenMakeTransition();
}
}
public ReadingsFragment() {
// Required empty public constructor
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_readings, container, false);
ButterKnife.bind(this, view);
textViewButtonInvitationReadings.setVisibility(View.INVISIBLE);
readingsBus = new AndroidBus();
readingsBus.register(this);
if (!MainActivity.userHasAPrepay) {
fabScan.setVisibility(View.VISIBLE);
fabScan.hide();
} else {
fabScan.setVisibility(View.GONE);
}
linechartCardView.setCardBackgroundColor(getResources().getColor(R.color.colorJmasBlueReadings));
fabChangeGraph.setEnabled(false);
textViewNoReadings.setVisibility(View.GONE);
/** Chart things **/
mCurrOverlapFactor = .5f;
mCurrEasing = new QuintEase();
mCurrStartX = -1;
mCurrStartY = 0;
mCurrAlpha = -1;
mOldOverlapFactor = 1;
mOldEasing = new QuintEase();
mOldStartX = -1;
mOldStartY = 0;
mOldAlpha = -1;
mHandler = new Handler();
initLineChart();
// the reason we want this on a handler is because it appears to look better if we
// let the transition finish and then animate our chart
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
fabScan.show();
if (!MainActivity.userHasAPrepay) {
animateInvitationTextFadeIn();
}
}
}, 500);
return view;
}
@Override
public void onResume() {
super.onResume();
// Set title
if (MainActivity.userHasAPrepay) {
MainActivity.bus.post(getResources().getString(R.string.toolbarTitleHistoricReadings));
} else {
MainActivity.bus.post(getResources().getString(R.string.toolbarTitleReadings));
}
if (MainActivity.ranAtLeastOnce) {
checkReadingsFromLocalDB(3);
}
}
/**
* Main Activity will call us when it finishes to get he data from the backend
*
* @param action just because we need an object to make the otto call
*/
@Subscribe
public void checkReadingsFromLocalDB(Integer action) {
mReadings = getReadingsForThisMonthRange(2);
time = new Time();
if (mReadings != null) {
long highestReading = 0;
long lowestReading = Integer.MAX_VALUE;
List<String> xAxisDays = new ArrayList<>();
chartValues = new float[mReadings.size()];
chartValuesForAnimation = new float[mReadings.size()];
String lastReadingDate = "";
int j = 0;
for (ChartReading i : mReadings) {
if (i.value > highestReading) {
highestReading = i.value;
}
if (i.value < lowestReading) {
lowestReading = i.value;
}
time.set(i.timeInMillis);
xAxisDays.add(time.format("%d/%m"));
lastReadingDate = time.format("%d/%m/%Y");
chartValues[j] = i.value;
if (!MainActivity.isFirstTime && i.timeInMillis > MainActivity.oldReadingsLastDateInMillis) {
chartValuesForAnimation[j] = lowestReading; // we want the lowest so we animate from there to the top
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
updatePoint();
}
}, 1000);
} else {
chartValuesForAnimation[j] = i.value;
}
j++;
}
String[] xAxisDaysArray = xAxisDays.toArray(new String[xAxisDays.size()]);
long totalForThisPeriod = highestReading - lowestReading;
updateUi(totalForThisPeriod, lastReadingDate);
// keep in mind that charValuesForAnimation are the same if there is no "new" reading to animate
updateLineChart(xAxisDaysArray, chartValuesForAnimation, lowestReading, highestReading);
fabChangeGraph.setEnabled(true);
MainActivity.ranAtLeastOnce = true;
} else {
progressBar.setVisibility(View.GONE);
textViewNoReadings.setVisibility(View.VISIBLE);
}
Logger.d("Finished checkReadingsFromLocalDB");
}
/**
* Will basically hide with a fade the indeterminate progress bar and will show the cardView
* and the chart
*/
private void updateUi(long totalForThisPeriod, String lastReadingDate) {
crossfade();
textViewTotalLitersForThisPeriod.setText(String.valueOf(totalForThisPeriod) + " m3");
textViewLastReadingDate.setText(lastReadingDate);
mLineChart.setVisibility(View.VISIBLE);
}
private void updateLineChart(String[] xAxisDaysArray, float[] valuesArray, long lowestReading, long highestReading) {
double tempSpacing = ((highestReading - lowestReading) / xAxisDaysArray.length);
int spacing = (int) Math.ceil(tempSpacing);
if (spacing == 0) {
spacing = 1;
}
mLineChart.reset();
LineSet dataSet = new LineSet(xAxisDaysArray, valuesArray);
dataSet.setColor(this.getResources().getColor(R.color.line))
.setThickness(Tools.fromDpToPx(3))
.setSmooth(true)
.setDashed(new float[]{10, 10})
.setDotsColor(this.getResources().getColor(R.color.colorPrimaryJmas))
.setDotsRadius(Tools.fromDpToPx(5))
.setDotsStrokeThickness(Tools.fromDpToPx(2))
.setDotsStrokeColor(this.getResources().getColor(R.color.line));
mLineChart.addData(dataSet);
mLineChart.setBorderSpacing(Tools.fromDpToPx(4))
.setLabelsFormat(new DecimalFormat("##' m3'"))
.setGrid(LineChartView.GridType.HORIZONTAL, mLineGridPaint)
.setXAxis(false)
.setXLabels(XController.LabelPosition.OUTSIDE)
.setYAxis(false)
.setYLabels(YController.LabelPosition.OUTSIDE)
.setAxisBorderValues((int) lowestReading, (int) highestReading, spacing)
.show(getAnimation(true).setEndAction(null))
;
mLineChart.animateSet(0, new DashAnimation());
}
/**
* DatabaseQuery
* gets all the readings for this month range
*
* @return >_>
*/
public static List<ChartReading> getReadingsForThisMonthRange(int range) {
Time time = new Time(Time.getCurrentTimezone());
time.setToNow();
return new Select()
.from(ChartReading.class)
// .orderBy("timeInMillis ASC")
// .where("month >= ?", time.month - range)
// .and("year = ?", time.year)
.execute();
}
private void crossfade() {
// Set the content view to 0% opacity but visible, so that it is visible
// (but fully transparent) during the animation.
linechartCardView.setAlpha(0f);
linechartCardView.setVisibility(View.VISIBLE);
// Animate the content view to 100% opacity, and clear any animation
// listener set on the view.
linechartCardView.animate()
.alpha(1f)
.setDuration(MainActivity.mShortAnimationDuration)
.setListener(null);
// Animate the loading view to 0% opacity. After the animation ends,
// set its visibility to GONE as an optimization step (it won't
// participate in layout passes, etc.)
progressBar.animate()
.alpha(0f)
.setDuration(MainActivity.mShortAnimationDuration)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
progressBar.setVisibility(View.GONE);
}
});
}
/**
* we need to know when the day ends this month
*
* @return an string array of the days
*/
public String[] getDaysToShowOnCalendar() {
List<String> days = new ArrayList<>();
int maxDay = Calendar.getInstance().getActualMaximum(Calendar.DAY_OF_MONTH);
int divider = maxDay / 5;
for (int i = 1; i < maxDay; i = i + divider) {
days.add(String.valueOf(i));
}
days.add(String.valueOf(maxDay));
String[] stringArray = days.toArray(new String[days.size()]);
return stringArray;
}
/**
* Chart things
*
* @param setIndex
* @param entryIndex
* @param rect
*/
@SuppressLint("NewApi")
private void showLineTooltip(int setIndex, int entryIndex, Rect rect) {
mLineTooltip = (TextView) getActivity().getLayoutInflater().inflate(R.layout.circular_tooltip, null);
mLineTooltip.setText(Integer.toString((int) chartValues[entryIndex]));
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams((int) Tools.fromDpToPx(35), (int) Tools.fromDpToPx(35));
layoutParams.leftMargin = rect.centerX() - layoutParams.width / 2;
layoutParams.topMargin = rect.centerY() - layoutParams.height / 2;
mLineTooltip.setLayoutParams(layoutParams);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
mLineTooltip.setPivotX(layoutParams.width / 2);
mLineTooltip.setPivotY(layoutParams.height / 2);
mLineTooltip.setAlpha(0);
mLineTooltip.setScaleX(0);
mLineTooltip.setScaleY(0);
mLineTooltip.animate()
.setDuration(150)
.alpha(1)
.scaleX(1).scaleY(1)
.rotation(360)
.setInterpolator(enterInterpolator);
}
time.set(mReadings.get(entryIndex).timeInMillis);
String selectedReadingDate = time.format("%d/%m/%Y");
textViewLastReadingDate.setText(selectedReadingDate);
mLineChart.showTooltip(mLineTooltip);
}
@SuppressLint("NewApi")
private void dismissLineTooltip(final int setIndex, final int entryIndex, final Rect rect) {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mLineTooltip.animate()
.setDuration(100)
.scaleX(0).scaleY(0)
.alpha(0)
.setInterpolator(exitInterpolator).withEndAction(new Runnable() {
@Override
public void run() {
mLineChart.removeView(mLineTooltip);
mLineTooltip = null;
if (entryIndex != -1)
showLineTooltip(setIndex, entryIndex, rect);
}
});
} else {
mLineChart.dismissTooltip(mLineTooltip);
mLineTooltip = null;
if (entryIndex != -1)
showLineTooltip(setIndex, entryIndex, rect);
}
}
private void initLineChart() {
mLineChart.setOnEntryClickListener(lineEntryListener);
mLineChart.setOnClickListener(lineClickListener);
mLineGridPaint = new Paint();
mLineGridPaint.setColor(this.getResources().getColor(R.color.colorPrimaryJmas400));
mLineGridPaint.setPathEffect(new DashPathEffect(new float[]{4, 4}, 0));
mLineGridPaint.setStyle(Paint.Style.STROKE);
mLineGridPaint.setAntiAlias(true);
mLineGridPaint.setStrokeWidth(Tools.fromDpToPx(.5f));
}
// private void hideChart() {
// mLineChart.dismiss(getAnimation(false).setEndAction(mExitEndAction));
// }
/**
* Hides the chart then after 500ms makes a transition
*/
private void hideChartThenMakeTransition() {
if (mLineTooltip != null) {
dismissLineTooltip(-1, -1, null);
}
mLineChart.dismiss(getAnimation(false).setEndAction(mMakeTransition));
}
/**
* Sets up a fragment and passes the parameters to make a shared element transition
*/
public void changeGraphClickedAction() {
Fragment billsFragment = new BillsFragment();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
makeAnimationBetweenFragments(
billsFragment, fabChangeGraph,
getResources().getString(R.string.transitionReadingsToBills),
android.R.transition.fade, // Exit Transition
android.R.transition.move); // Enter Transition
} else {
MainActivity.bus.post(billsFragment);
}
fabChangeGraph.setEnabled(true);
isAnimationRunning = false;
}
/**
* This will make a transition with a shared element, in this case the CardView is the shared element
*
* @param fragment the fragment that will be used to replace this one
* @param sharedView the shared element between the fragments
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void makeAnimationBetweenFragments(Fragment fragment, View sharedView, String sharedTransitionName, int exitTransition, int enterTransition) {
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
assert fragmentManager != null;
setSharedElementReturnTransition(TransitionInflater.from(getActivity()).inflateTransition(R.transition.trans_test));
setExitTransition(TransitionInflater.from(getActivity()).inflateTransition(exitTransition));
fragment.setSharedElementEnterTransition(TransitionInflater.from(getActivity()).inflateTransition(R.transition.trans_test));
fragment.setEnterTransition(TransitionInflater.from(getActivity()).inflateTransition(enterTransition));
fragmentManager.beginTransaction()
.replace(R.id.container, fragment)
.addToBackStack(null)
.addSharedElement(sharedView, sharedTransitionName)
.commit();
Log.d(TAG, "fragment added with transition " + fragment.getTag());
}
/**
* Updates the chart to show a "significant" way that there has been a new reading
*/
private void updatePoint() {
try {
Crouton.makeText(getActivity(), "Nueva Lectura", new Style.Builder().setBackgroundColor(R.color.blue_400).build(), linechartCardView).show();
mLineChart.updateValues(0, chartValues);
mLineChart.notifyDataUpdate();
} catch (Exception e) {
Logger.e(e, "Error trying to update the chart last point");
}
}
public void animateInvitationTextFadeIn() {
textViewButtonInvitationReadings.setAlpha(0f);
textViewButtonInvitationReadings.setVisibility(View.VISIBLE);
textViewButtonInvitationReadings.animate()
.alpha(1f)
.setDuration(1000)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
}
});
}
private Animation getAnimation(boolean newAnim) {
if (newAnim)
return new Animation()
.setAlpha(mCurrAlpha)
.setEasing(mCurrEasing)
.setOverlap(mCurrOverlapFactor, mCurrOverlapOrder)
.setStartPoint(mCurrStartX, mCurrStartY);
else
return new Animation()
.setAlpha(mOldAlpha)
.setEasing(mOldEasing)
.setOverlap(mOldOverlapFactor, mOldOverlapOrder)
.setStartPoint(mOldStartX, mOldStartY);
}
}