package com.fourtails.usuariolecturista.fragments;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.res.Resources;
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.Fragment;
import android.support.v4.app.FragmentManager;
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.Button;
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.model.Point;
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.model.ChartBill;
import com.fourtails.usuariolecturista.ottoEvents.AndroidBus;
import com.fourtails.usuariolecturista.ottoEvents.CreateNewBillEvent;
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.List;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
import de.keyboardsurfer.android.widget.crouton.Configuration;
import de.keyboardsurfer.android.widget.crouton.Crouton;
import static com.fourtails.usuariolecturista.MainActivity.allowUserToPrepay;
import static com.fourtails.usuariolecturista.MainActivity.bus;
import static com.fourtails.usuariolecturista.MainActivity.prepayModeEnabled;
import static com.fourtails.usuariolecturista.MainActivity.userHasAPrepay;
/**
* A simple {@link Fragment} subclass.
*/
public class BillsFragment extends Fragment {
public static final String TAG = "BillsFragment";
public static Bus billsBus;
/**
* 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;
public static double selectedBill;
/**
* Line
*/
//private static LineChartView mLineChart;
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(entryIndex, rect);
else
dismissLineTooltip(entryIndex, rect);
}
};
private final View.OnClickListener lineClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mLineTooltip != null)
dismissLineTooltip(-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);
}
};
/**
* fires after the drawing of the last chart
*/
private final Runnable mAnimatePoint = new Runnable() {
@Override
public void run() {
mHandler.postDelayed(new Runnable() {
public void run() {
addPoint();
}
}, 500);
}
};
private boolean isAnimationRunning = false;
@Bind(R.id.lineChartBills)
LineChartView mLineChart;
/**
* Injected views and clickListeners ********************************************************
*/
@Bind(R.id.fabPay)
FloatingActionButton fabPay;
@Bind(R.id.fabChangeGraphBills)
FloatingActionButton fabChangeGraphBills;
@Bind(R.id.cardViewBills)
CardView lineChartCardViewBills;
@Bind(R.id.cardViewBillsBottom)
CardView sharedCardView;
@Bind(R.id.textViewNoBillsMsg)
TextView textViewNoBills;
@Bind(R.id.textViewBillingDateBills)
TextView textViewBillingDateBills;
@Bind(R.id.textViewSelectedBills)
TextView textViewSelectedBill;
@Bind(R.id.textViewBillsStatus)
TextView textViewBillsStatus;
@Bind(R.id.textViewButtonInvitationBills)
TextView textViewPrepayInvitation;
@Bind(R.id.buttonNewBill)
Button buttonNewBill;
String selectedBillStatus;
private float[] chartValues;
private List<ChartBill> mBills;
public static int selectedBillIndex;
Time time;
@OnClick(R.id.fabPay)
public void payButtonClicked() {
if (prepayModeEnabled) {
Fragment prepaidFragment = new PrepayCalculatorFragment();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
makeAnimationBetweenFragments(
prepaidFragment, sharedCardView,
getResources().getString(R.string.transitionFirstCardView),
android.R.transition.fade, // Exit Transition
android.R.transition.move); // Enter Transition
} else {
bus.post(prepaidFragment); // Non lollipop
}
} else {
if (mBills.get(selectedBillIndex).status.equalsIgnoreCase("Unpaid")) {
Fragment payOptionsFragment = new PayOptionsFragment();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
makeAnimationBetweenFragments(
payOptionsFragment, sharedCardView,
getResources().getString(R.string.transitionFirstCardView),
android.R.transition.fade, // Exit Transition
android.R.transition.move); // Enter Transition
} else {
bus.post(payOptionsFragment); // Non lollipop
}
}
}
}
@OnClick(R.id.fabChangeGraphBills)
public void changeGraphClicked() {
if (!isAnimationRunning) {
fabChangeGraphBills.setEnabled(false);
isAnimationRunning = true;
hideChartThenMakeTransition();
}
}
@OnClick(R.id.buttonNewBill)
public void newBillClicked() {
if (mLineTooltip != null) {
dismissLineTooltip(-1, null);
}
buttonNewBill.setEnabled(false);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
buttonNewBill.setEnabled(true);
}
}, 2000);
MainActivity.bus.post(new CreateNewBillEvent(CreateNewBillEvent.Type.STARTED, 1));
}
public BillsFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_bills, container, false);
ButterKnife.bind(this, view);
billsBus = new AndroidBus();
billsBus.register(this);
if (MainActivity.userHasAPrepay) { // User has a prepay (negative balance)
fabPay.setVisibility(View.GONE);
} else {
fabPay.setVisibility(View.VISIBLE);
fabPay.hide();
}
lineChartCardViewBills.setCardBackgroundColor(getResources().getColor(R.color.colorPrimaryJmas600));
textViewNoBills.setVisibility(View.GONE);
if (prepayModeEnabled) {
fabPay.setImageDrawable(getResources().getDrawable(R.drawable.ic_schedule_white_24dp));
}
/** 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() {
checkBillsFromLocalDB();
}
}, 500);
fabPay.hide();
return view;
}
@Override
public void onResume() {
super.onResume();
// Set title
if (MainActivity.userHasAPrepay) {
bus.post(getResources().getString(R.string.toolbarTitleHistoricBills));
} else {
bus.post(getResources().getString(R.string.toolbarTitleBills));
}
if (prepayModeEnabled) {
fabPay.setImageDrawable(getResources().getDrawable(R.drawable.ic_schedule_white_24dp));
} else {
fabPay.setImageDrawable(getResources().getDrawable(R.drawable.ic_payment_white_24dp));
}
}
/**
* This is basically just a refresh for when we pay
*
* @param option most of the time 1
*/
@Subscribe
public void updateBills(Integer option) {
if (option == 1) {
checkBillsFromLocalDB();
}
}
/**
* This might be the most important method here, it basically just gets the bills and puts them
* into the chart
* TODO: get the unpaid bills in a different array
*/
public void checkBillsFromLocalDB() {
mBills = getBillsForThisMonthRange(2);
time = new Time();
if (mBills != null) {
double highestReading = 0;
double lowestReading = Integer.MAX_VALUE;
List<String> xAxisDays = new ArrayList<>();
chartValues = new float[mBills.size()];
String lastBillStatus = "";
double lastBillAmount = 0.0;
String lastReadingDate = "";
int j = 0;
for (ChartBill i : mBills) {
if (i.amount > highestReading) {
highestReading = i.amount;
}
if (i.amount < lowestReading) {
lowestReading = i.amount;
}
time.set(i.timeInMillis);
xAxisDays.add(time.format("%d/%m"));
lastReadingDate = time.format("%d/%m/%Y");
selectedBillIndex = j;
chartValues[j++] = (float) i.amount;
lastBillStatus = i.status;
lastBillAmount = i.amount;
}
String[] xAxisDaysArray = xAxisDays.toArray(new String[xAxisDays.size()]);
updateUi(lastBillAmount, lastBillStatus, lastReadingDate);
try {
updateLineChart(xAxisDaysArray, chartValues, lowestReading, highestReading);
} catch (Exception e) {
Logger.e(e, "The user most likely pressed back a bunch of times");
}
} else {
lineChartCardViewBills.setVisibility(View.GONE);
textViewNoBills.setVisibility(View.VISIBLE);
}
Logger.d("Finished checkBillsFromLocalDB");
}
/**
* Update the textViews
*
* @param lastBillAmount last bill
* @param lastBillStatus last status
* @param lastReadingDate
*/
private void updateUi(double lastBillAmount, String lastBillStatus, String lastReadingDate) {
textViewSelectedBill.setText("$" + String.valueOf(lastBillAmount));
textViewBillingDateBills.setText(lastReadingDate);
String statusTranslate;
selectedBill = lastBillAmount;
if (prepayModeEnabled && allowUserToPrepay) {
buttonNewBill.setVisibility(View.GONE);
fabPay.setImageDrawable(getResources().getDrawable(R.drawable.ic_schedule_white_24dp));
fabPay.show();
animateInvitationTextFadeIn(getString(R.string.billsLabelsPrepayInvitation));
statusTranslate = "Pagada";
} else {
fabPay.setImageDrawable(getResources().getDrawable(R.drawable.ic_payment_white_24dp));
if (lastBillStatus.equalsIgnoreCase("Paid")) {
statusTranslate = "Pagada";
animateInvitationTextFadeOut();
fabPay.hide();
} else if (lastBillStatus.equalsIgnoreCase("UnPaid")) {
statusTranslate = "No Pagada";
animateInvitationTextFadeIn(getString(R.string.billsLabelsPayInvitation));
fabPay.show();
} else {
statusTranslate = "Desconocido";
animateInvitationTextFadeOut();
fabPay.hide();
}
if (MainActivity.showNewBillButton && !userHasAPrepay) {
buttonNewBill.setVisibility(View.VISIBLE);
showDebtCrouton();
} else {
buttonNewBill.setVisibility(View.GONE);
}
}
textViewBillsStatus.setText(statusTranslate);
mLineChart.setVisibility(View.VISIBLE);
}
public void animateInvitationTextFadeIn(String textToShow) {
textViewPrepayInvitation.setAlpha(0f);
textViewPrepayInvitation.setText(textToShow);
textViewPrepayInvitation.setVisibility(View.VISIBLE);
textViewPrepayInvitation.animate()
.alpha(1f)
.setDuration(1000)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
}
});
}
public void animateInvitationTextFadeOut() {
textViewPrepayInvitation.setAlpha(1f);
textViewPrepayInvitation.animate()
.alpha(0f)
.setDuration(500)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
textViewPrepayInvitation.setVisibility(View.GONE);
}
});
}
/**
* DatabaseQuery
* gets all the readings for this month range
*
* @return >_>
*/
public static List<ChartBill> getBillsForThisMonthRange(int range) {
Time time = new Time(Time.getCurrentTimezone());
time.setToNow();
return new Select()
.from(ChartBill.class)
.orderBy("timeInMillis ASC")
// .where("month >= ?", time.month - range)
// .and("year = ?", time.year)
.execute();
}
/**
* Hides the chart then after 500ms makes a transition
*/
private void hideChartThenMakeTransition() {
try {
if (mLineTooltip != null) {
dismissLineTooltip(-1, null);
}
mLineChart.dismiss(getAnimation(false).setEndAction(mMakeTransition));
} catch (Exception e) {
Logger.e(e, "Something went wrong trying to hide the chart");
}
}
/**
* Sets up a fragment and passes the parameters to make a shared element transition
*/
private void changeGraphClickedAction() {
Fragment readingsFragment = new ReadingsFragment();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
makeAnimationBetweenFragments(
readingsFragment, fabChangeGraphBills,
getResources().getString(R.string.transitionReadingsToBills),
android.R.transition.fade, // Exit Transition
android.R.transition.move); // Enter Transition
} else {
bus.post(readingsFragment);
}
fabChangeGraphBills.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());
}
/**
* Chart things
*
* @param entryIndex
* @param rect
*/
@SuppressLint("NewApi")
private void showLineTooltip(int entryIndex, Rect rect) {
try {
selectedBillIndex = entryIndex;
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 (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);
}
selectedBill = chartValues[entryIndex];
selectedBillStatus = mBills.get(entryIndex).status;
time.set(mBills.get(entryIndex).timeInMillis);
String selectedDate = time.format("%d/%m/%Y");
// Prepay logic
if (prepayModeEnabled) {
selectedBillStatus = "Pagada";
} else {
if (selectedBillStatus.equalsIgnoreCase("Paid")) {
selectedBillStatus = "Pagada";
animateInvitationTextFadeOut();
fabPay.hide();
} else if (selectedBillStatus.equalsIgnoreCase("UnPaid")) {
selectedBillStatus = "No Pagada";
animateInvitationTextFadeIn(getString(R.string.billsLabelsPayInvitation));
fabPay.show();
} else {
selectedBillStatus = "Desconocido";
animateInvitationTextFadeOut();
fabPay.hide();
}
}
textViewBillsStatus.setText(selectedBillStatus);
textViewSelectedBill.setText("$" + String.valueOf(selectedBill));
textViewBillingDateBills.setText(selectedDate);
mLineChart.showTooltip(mLineTooltip);
} catch (Exception e) {
Logger.e("why is this crash happening?");
e.printStackTrace();
}
}
@SuppressLint("NewApi")
private void dismissLineTooltip(final int entryIndex, final Rect rect) {
try {
if (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(entryIndex, rect);
}
});
} else {
mLineChart.dismissTooltip(mLineTooltip);
mLineTooltip = null;
if (entryIndex != -1)
showLineTooltip(entryIndex, rect);
}
} catch (Exception e) {
Logger.e("why is this crash happening?");
e.printStackTrace();
}
}
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 updateLineChart(String[] xAxisDaysArray, float[] chartValues, double lowestReading, double highestReading) {
try {
double tempSpacing = ((highestReading - lowestReading) / xAxisDaysArray.length);
int spacing = (int) Math.ceil(tempSpacing);
if (spacing == 0) {
spacing = 1;
}
mLineChart.reset();
// this will change the dots color making the "Unpaid" ones in red
LineSet dataSet = new LineSet();
Point point;
for (int i = 0; i < xAxisDaysArray.length; i++) {
point = new Point(xAxisDaysArray[i], chartValues[i]);
if (mBills.get(i).status.equalsIgnoreCase("Unpaid")) {
point.setColor(this.getResources().getColor(R.color.red_300));
} else {
point.setColor(this.getResources().getColor(R.color.colorJmasBlueReadings));
}
dataSet.addPoint(point);
}
dataSet.setDotsRadius(Tools.fromDpToPx(5))
.setDotsStrokeThickness(Tools.fromDpToPx(2))
.setDotsStrokeColor(this.getResources().getColor(R.color.line))
.setThickness(Tools.fromDpToPx(3))
.setColor(this.getResources().getColor(R.color.whiteWater))
.setDashed(new float[]{10, 10});
mLineChart.addData(dataSet);
mLineChart.setBorderSpacing(Tools.fromDpToPx(4))
.setLabelsFormat(new DecimalFormat("'$ '##"))
.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());
} catch (Resources.NotFoundException e) {
Logger.e("why is this crash happening?");
e.printStackTrace();
}
}
private void addPoint() {
float[] thing = {0f, 25f, 26f, 39f, 42f, 30f, 100f};
mLineChart.updateValues(0, thing);
mLineChart.notifyDataUpdate();
}
/**
* Shows am infinite crouton to show image is being upload to the server
*/
private void showDebtCrouton() {
LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.crouton_normal_custom_view, null);
TextView textView = (TextView) view.findViewById(R.id.textViewNormalCustomCrouton);
textView.setText("Tiene adeudo en su cuenta, por favor cree una nueva factura para pagar");
Configuration configuration = new Configuration.Builder()
.setDuration(Configuration.DURATION_LONG)
.build();
Crouton crouton;
crouton = Crouton.make(getActivity(), view, mLineChart).setConfiguration(configuration);
crouton.show();
}
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);
}
}