/**
* Copyright (C) 2013 - 2015 the enviroCar community
* <p>
* This file is part of the enviroCar app.
* <p>
* The enviroCar app 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.
* <p>
* The enviroCar app 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.
* <p>
* You should have received a copy of the GNU General Public License along
* with the enviroCar app. If not, see http://www.gnu.org/licenses/.
*/
package org.envirocar.app.view.logbook;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v7.widget.Toolbar;
import android.text.InputFilter;
import android.text.Spanned;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import com.afollestad.materialdialogs.MaterialDialog;
import org.envirocar.app.R;
import org.envirocar.app.handler.CarPreferenceHandler;
import org.envirocar.app.view.utils.ECAnimationUtils;
import org.envirocar.core.entity.Car;
import org.envirocar.core.entity.Fueling;
import org.envirocar.core.entity.FuelingImpl;
import org.envirocar.core.exception.DataCreationFailureException;
import org.envirocar.core.exception.NotConnectedException;
import org.envirocar.core.exception.ResourceConflictException;
import org.envirocar.core.exception.UnauthorizedException;
import org.envirocar.core.injection.BaseInjectorFragment;
import org.envirocar.core.logging.Logger;
import org.envirocar.remote.DAOProvider;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import butterknife.ButterKnife;
import butterknife.InjectView;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
import rx.subscriptions.CompositeSubscription;
/**
* TODO JavaDoc
*
* @author dewall
*/
public class LogbookAddFuelingFragment extends BaseInjectorFragment {
private static final Logger LOG = Logger.getLogger(LogbookAddFuelingFragment.class);
private static final DecimalFormat DECIMAL_FORMATTER_2 = new DecimalFormat("#.##");
private static final DecimalFormat DECIMAL_FORMATTER_3 = new DecimalFormat("#.###");
static{
DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.GERMAN);
symbols.setDecimalSeparator('.');
DECIMAL_FORMATTER_2.setDecimalFormatSymbols(symbols);
DECIMAL_FORMATTER_3.setDecimalFormatSymbols(symbols);
}
@InjectView(R.id.logbook_layout_addfueling_toolbar)
protected Toolbar addFuelingToolbar;
@InjectView(R.id.activity_logbook_add_fueling_card_content)
protected View contentView;
@InjectView(R.id.activity_logbook_add_fueling_car_selection)
protected Spinner addFuelingCarSelection;
@InjectView(R.id.logbook_add_fueling_milagetext)
protected EditText addFuelingMilageText;
@InjectView(R.id.logbook_add_fueling_volumetext)
protected EditText addFuelingVolumeText;
@InjectView(R.id.logbook_add_fueling_totalpricetext)
protected EditText addFuelingTotalCostText;
@InjectView(R.id.logbook_add_fueling_priceperlitretext)
protected EditText addFuelingPricePerLitreText;
@InjectView(R.id.logbook_add_fueling_partialfueling_checkbox)
protected CheckBox partialFuelingCheckbox;
@InjectView(R.id.logbook_add_fueling_missedfueling_checkbox)
protected CheckBox missedFuelingCheckbox;
@InjectView(R.id.logbook_add_fueling_comment)
protected EditText commentText;
@InjectView(R.id.layout_general_info_background)
protected View infoBackground;
@InjectView(R.id.layout_general_info_background_img)
protected ImageView infoBackgroundImg;
@InjectView(R.id.layout_general_info_background_firstline)
protected TextView infoBackgroundFirst;
@InjectView(R.id.layout_general_info_background_secondline)
protected TextView infoBackgroundSecond;
@Inject
protected CarPreferenceHandler carHandler;
@Inject
protected DAOProvider daoProvider;
private CompositeSubscription subscriptions = new CompositeSubscription();
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
// Inflate the view and inject the annotated view.
View view = inflater.inflate(R.layout.activity_logbook_add_fueling_card, container, false);
ButterKnife.inject(this, view);
addFuelingToolbar.setNavigationIcon(R.drawable.ic_close_white_24dp);
addFuelingToolbar.inflateMenu(R.menu.menu_logbook_add_fueling);
addFuelingToolbar.setNavigationOnClickListener(v ->
((LogbookUiListener) getActivity()).onHideAddFuelingCard());
addFuelingToolbar.setOnMenuItemClickListener(item -> {
onClickAddFueling();
return true;
});
initTextViews();
Car selectedCar = carHandler.getCar();
List<Car> addedCars = carHandler.getDeserialzedCars();
// Inflate the values for the car spinner.
LogbookCarSpinnerAdapter carSpinnerAdapter = new LogbookCarSpinnerAdapter(getActivity(),
addedCars);
addFuelingCarSelection.setAdapter(carSpinnerAdapter);
if (selectedCar != null) {
// Set the position of the car inside the spinner as default.
int spinnerPosition = carSpinnerAdapter.getPosition(selectedCar);
addFuelingCarSelection.setSelection(spinnerPosition);
} else if (addedCars.isEmpty()) {
// Show a notification that there is no selected car
contentView.setVisibility(View.GONE);
infoBackgroundImg.setImageResource(R.drawable.img_car);
infoBackgroundFirst.setText(R.string.logbook_background_no_cars_first);
infoBackgroundSecond.setText(R.string.logbook_background_no_cars_second);
ECAnimationUtils.animateShowView(getContext(), infoBackground, R.anim.fade_in);
}
return view;
}
@Override
public void onDestroy() {
if (!subscriptions.isUnsubscribed()) {
subscriptions.unsubscribe();
}
super.onDestroy();
}
private void onClickAddFueling() {
// Reset the errors.
addFuelingMilageText.setError(null);
addFuelingTotalCostText.setError(null);
addFuelingVolumeText.setError(null);
boolean formError = false;
View focusView = null;
if (addFuelingMilageText.getText() == null || addFuelingMilageText.getText().toString()
.equals("")) {
addFuelingMilageText.setError(getString(R.string.logbook_error_form_blank_input));
focusView = addFuelingMilageText;
formError = true;
}
if (addFuelingTotalCostText.getText() == null || addFuelingTotalCostText.getText()
.toString().equals("")) {
addFuelingTotalCostText.setError(getString(R.string.logbook_error_form_blank_input));
focusView = addFuelingTotalCostText;
formError = true;
}
if (addFuelingVolumeText.getText() == null || addFuelingVolumeText.getText()
.toString().equals("")) {
addFuelingVolumeText.setError(getString(R.string.logbook_error_form_blank_input));
focusView = addFuelingVolumeText;
formError = true;
}
if (formError) {
LOG.info("Error on input form.");
focusView.requestFocus();
return;
}
Car car = (Car) addFuelingCarSelection.getSelectedItem();
if (car == null) {
LOG.info("Cant create fueling entry, because the car is empty");
Snackbar.make(addFuelingToolbar,
"You must have selected a car type for creating a fueling.",
Snackbar.LENGTH_LONG).show();
return;
}
Double cost = null, milage = null, volume = null;
try {
cost = getEditTextDoubleValue(addFuelingTotalCostText.getText().toString());
milage = getEditTextDoubleValue(addFuelingMilageText.getText().toString());
volume = getEditTextDoubleValue(addFuelingVolumeText.getText().toString());
} catch (ParseException e) {
formError = true;
if (cost == null) {
LOG.error(String.format("Invalid input text -> [%s]", addFuelingTotalCostText
.toString()), e);
addFuelingTotalCostText.setError(getString(R.string.logbook_invalid_input));
focusView = addFuelingTotalCostText;
} else if (milage == null) {
LOG.error(String.format("Invalid input text -> [%s]", addFuelingMilageText
.toString()), e);
addFuelingMilageText.setError(getString(R.string.logbook_invalid_input));
focusView = addFuelingMilageText;
} else {
LOG.error(String.format("Invalid input text -> [%s]", addFuelingVolumeText
.toString()), e);
addFuelingVolumeText.setError(getString(R.string.logbook_invalid_input));
focusView = addFuelingVolumeText;
}
}
if (formError) {
LOG.info("Error on input form.");
focusView.requestFocus();
return;
}
boolean missedFuelStop = missedFuelingCheckbox.isChecked();
boolean partialFueling = partialFuelingCheckbox.isChecked();
Fueling fueling = new FuelingImpl();
fueling.setTime(System.currentTimeMillis());
fueling.setCar(car);
fueling.setCost(cost, Fueling.CostUnit.EURO);
fueling.setVolume(volume, Fueling.VolumeUnit.LITRES);
fueling.setMilage(milage, Fueling.MilageUnit.KILOMETRES);
fueling.setMissedFuelStop(missedFuelStop);
fueling.setPartialFueling(partialFueling);
if (commentText.getText() != null) {
String comment = commentText.getText().toString();
if (comment != null && !comment.isEmpty()) {
fueling.setComment(comment);
}
}
// upload the fueling
if (car.getId() == null || !car.getId().isEmpty()) {
uploadCarBeforeFueling(car, fueling);
} else {
uploadFueling(fueling);
}
}
private void initTextViews() {
addFuelingMilageText.setFilters(new InputFilter[]{
new DigitsInputFilter(addFuelingMilageText, 7)});
addFuelingMilageText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
String milage = addFuelingMilageText.getText().toString();
if (milage != null && !milage.isEmpty()) {
if (hasFocus) {
addFuelingMilageText.setText(milage.split(" ")[0]);
} else {
addFuelingMilageText.setText(milage + " km");
}
}
}
});
addFuelingVolumeText.setFilters(new InputFilter[]{
new DigitsInputFilter(addFuelingVolumeText, 3, 2)});
addFuelingVolumeText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
String volumeText = addFuelingVolumeText.getText().toString();
if (volumeText == null || volumeText.isEmpty())
return;
if (!hasFocus) {
if (volumeText != null && !volumeText.isEmpty()) {
addFuelingVolumeText.setText(volumeText + " l");
try {
if (hasEditTextValue(addFuelingPricePerLitreText)) {
setTotalPriceValue(getEditTextDoubleValue(volumeText) *
getEditTextDoubleValue(addFuelingPricePerLitreText));
} else if (hasEditTextValue(addFuelingTotalCostText)) {
setPricePerLitreValue(getEditTextDoubleValue
(addFuelingTotalCostText)
/ getEditTextDoubleValue(volumeText));
}
} catch (ParseException e) {
LOG.error(e.getMessage(), e);
}
}
} else {
if (volumeText != null && !volumeText.isEmpty()) {
addFuelingVolumeText.setText(volumeText.split(" ")[0]);
}
}
}
});
addFuelingPricePerLitreText.setFilters(new InputFilter[]{
new DigitsInputFilter(addFuelingPricePerLitreText, 2, 3)});
addFuelingPricePerLitreText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
String pricePerLitre = addFuelingPricePerLitreText.getText().toString();
if (pricePerLitre == null || pricePerLitre.isEmpty())
return;
if (!hasFocus) {
addFuelingPricePerLitreText.setText(pricePerLitre + " €/l");
try {
if (hasEditTextValue(addFuelingVolumeText)) {
setTotalPriceValue(getEditTextDoubleValue(addFuelingVolumeText) *
getEditTextDoubleValue(pricePerLitre));
} else if (hasEditTextValue(addFuelingTotalCostText)) {
setVolumeValue(getEditTextDoubleValue(addFuelingTotalCostText) /
getEditTextDoubleValue(pricePerLitre));
}
} catch (ParseException e) {
LOG.error(e.getMessage(), e);
}
} else {
addFuelingPricePerLitreText.setText(pricePerLitre.split(" ")[0]);
}
}
});
addFuelingTotalCostText.setFilters(new InputFilter[]{
new DigitsInputFilter(addFuelingTotalCostText, 3, 2)});
addFuelingTotalCostText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
String totalCost = addFuelingTotalCostText.getText().toString();
if (totalCost == null || totalCost.isEmpty())
return;
if (!hasFocus) {
addFuelingTotalCostText.setText(totalCost + " €");
try {
if (hasEditTextValue(addFuelingVolumeText)) {
setPricePerLitreValue(getEditTextDoubleValue(totalCost) /
getEditTextDoubleValue(addFuelingVolumeText));
} else if (hasEditTextValue(addFuelingPricePerLitreText)) {
setVolumeValue(getEditTextDoubleValue(totalCost)
/ getEditTextDoubleValue(addFuelingPricePerLitreText));
}
} catch (ParseException e) {
LOG.error(e.getMessage(), e);
}
} else {
if (totalCost != null && !totalCost.isEmpty()) {
addFuelingTotalCostText.setText(totalCost.split(" ")[0]);
}
}
}
});
}
private void uploadCarBeforeFueling(final Car car, final Fueling fueling) {
subscriptions.add(daoProvider.getSensorDAO()
.createCarObservable(car)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Car>() {
private MaterialDialog dialog;
@Override
public void onStart() {
LOG.info("uploadCarBeforeFueling() has started");
dialog = new MaterialDialog.Builder(getContext())
.progress(true, 0)
.title(R.string.logbook_dialog_uploading_fueling_header)
.content(R.string.logbook_dialog_uploading_fueling_car)
.cancelable(false)
.show();
}
@Override
public void onNext(Car car) {
// car has been successfully uploaded
LOG.info(String.format(
"uploadCarBeforeFueling(): car has been uploaded -> [%s]",
car.getId()));
}
@Override
public void onCompleted() {
LOG.info("uploadCarBeforeFueling(): was successful.");
dialog.dismiss();
// car upload was sucessful. Now upload the fueling.
uploadFueling(fueling);
}
@Override
public void onError(Throwable e) {
LOG.error(e.getMessage(), e);
if (e instanceof NotConnectedException) {
showSnackbarInfo(R.string.logbook_error_communication);
} else if (e instanceof DataCreationFailureException) {
showSnackbarInfo(R.string.logbook_error_resource_conflict);
} else if (e instanceof UnauthorizedException) {
showSnackbarInfo(R.string.logbook_error_unauthorized);
} else {
showSnackbarInfo(R.string.logbook_error_general);
}
dialog.dismiss();
}
}));
}
/**
* Uploads the fueling to the enviroCar Server.
*
* @param fueling the fueling to upload.
*/
private void uploadFueling(Fueling fueling) {
subscriptions.add(daoProvider.getFuelingDAO().createFuelingObservable(fueling)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Void>() {
private MaterialDialog dialog;
@Override
public void onStart() {
LOG.info("Started the creation of a fueling.");
dialog = new MaterialDialog.Builder(getContext())
.progress(true, 0)
.title(R.string.logbook_dialog_uploading_fueling_header)
.content(R.string.logbook_dialog_uploading_fueling_content)
.cancelable(false)
.show();
}
@Override
public void onCompleted() {
LOG.info(String.format("Successfully uploaded fueling -> [%s]", fueling
.getRemoteID()));
dialog.dismiss();
((LogbookUiListener) getActivity()).onFuelingUploaded(fueling);
((LogbookUiListener) getActivity()).onHideAddFuelingCard();
}
@Override
public void onError(Throwable e) {
LOG.error(e.getMessage(), e);
if (e instanceof NotConnectedException) {
showSnackbarInfo(R.string.logbook_error_communication);
} else if (e instanceof ResourceConflictException) {
showSnackbarInfo(R.string.logbook_error_resource_conflict);
} else if (e instanceof UnauthorizedException) {
showSnackbarInfo(R.string.logbook_error_unauthorized);
}
dialog.dismiss();
}
@Override
public void onNext(Void aVoid) {
// Nothing to do
}
}));
}
private void setVolumeValue(double volume) {
addFuelingVolumeText.setText(DECIMAL_FORMATTER_2.format(volume) + " l");
}
private void setPricePerLitreValue(double price) {
addFuelingPricePerLitreText.setText(DECIMAL_FORMATTER_3.format(price) + " €/l");
}
private void setTotalPriceValue(double value) {
addFuelingTotalCostText.setText(
(DECIMAL_FORMATTER_2.format(value) + " €").replaceAll(",", "."));
}
private boolean hasEditTextValue(EditText input) {
String value = input.getText().toString();
return value != null && !value.isEmpty();
}
private double getEditTextDoubleValue(EditText input) throws ParseException {
try {
return getEditTextDoubleValue(input.getText().toString());
} catch (ParseException e) {
LOG.error(String.format("Invalid input text -> [%s]", input.toString()), e);
addFuelingTotalCostText.setError(getString(R.string.logbook_invalid_input));
addFuelingTotalCostText.requestFocus();
throw e;
}
}
private double getEditTextDoubleValue(String input) throws ParseException {
String[] yea = input.split(" ");
if(yea.length == 0){
return 0.0;
}
String toParse = yea[0].replace(",", ".");
try{
return Double.parseDouble(toParse);
} catch (NumberFormatException e){
LOG.error(String.format("Error while parsing double [%s]", toParse), e);
throw new ParseException(e.getMessage(), 0);
}
}
private void showSnackbarInfo(int resourceID) {
Snackbar.make(addFuelingToolbar, resourceID, Snackbar.LENGTH_LONG).show();
}
private class DigitsInputFilter implements InputFilter {
private final Pattern pattern;
private final EditText editText;
public DigitsInputFilter(final EditText editText, int digitsBefore) {
this(editText, digitsBefore, -1);
}
/**
* Constructor.
*
* @param digitsBefore
* @param digitsAfter
*/
public DigitsInputFilter(final EditText editText, int digitsBefore, int digitsAfter) {
String pattern = "^(\\d{0," + (digitsBefore) + "})";
if (digitsAfter > 0) {
pattern += "(\\.(\\d{1," + (digitsAfter) + "})?)?$";
}
this.pattern = Pattern.compile(pattern);
this.editText = editText;
}
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int
dstart, int dend) {
if (source.toString().contains(" ")) {
addFuelingPricePerLitreText.setError(null);
addFuelingVolumeText.setError(null);
addFuelingTotalCostText.setError(null);
// The string value contains a unit. Therefore split the value and show an error
// if the splitted value does not match.
if(source.toString().split(" ").length == 0)
return "";
Matcher matcher = pattern.matcher(source.toString().split(" ")[0]);
if (!matcher.matches()) {
addFuelingPricePerLitreText.setError(getString(R.string.logbook_invalid_input));
addFuelingVolumeText.setError(getString(R.string.logbook_invalid_input));
addFuelingTotalCostText.setError(getString(R.string.logbook_invalid_input));
}
return null;
}
String complete = dest.toString() + source.toString();
Matcher matcher = pattern.matcher(complete);
if (!matcher.matches()) {
return "";
}
return null;
}
}
}