package org.envirocar.app.view.carselection;
import android.graphics.Point;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Pair;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.TextView;
import com.jakewharton.rxbinding.support.v7.widget.RxToolbar;
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.CarImpl;
import org.envirocar.core.injection.BaseInjectorFragment;
import org.envirocar.core.logging.Logger;
import org.envirocar.remote.DAOProvider;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.inject.Inject;
import butterknife.ButterKnife;
import butterknife.InjectView;
import rx.Scheduler;
import rx.Subscriber;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
/**
* TODO JavaDoc
*
* @author dewall
*/
public class CarSelectionAddCarFragment extends BaseInjectorFragment {
private static final Logger LOG = Logger.getLogger(CarSelectionAddCarFragment.class);
@InjectView(R.id.activity_car_selection_newcar_toolbar)
protected Toolbar toolbar;
@InjectView(R.id.activity_car_selection_newcar_toolbar_exp)
protected View toolbarExp;
@InjectView(R.id.activity_car_selection_newcar_content_view)
protected View contentView;
@InjectView(R.id.activity_car_selection_newcar_download_layout)
protected View downloadView;
@InjectView(R.id.activity_car_selection_newcar_manufacturer)
protected TextView manufacturerText;
@InjectView(R.id.activity_car_selection_newcar_manufacturer_spinner)
protected Spinner manufacturerSpinner;
@InjectView(R.id.activity_car_selection_newcar_model)
protected TextView modelText;
@InjectView(R.id.activity_car_selection_newcar_model_spinner)
protected Spinner modelSpinner;
@InjectView(R.id.activity_car_selection_newcar_year)
protected TextView yearText;
@InjectView(R.id.activity_car_selection_newcar_year_spinner)
protected Spinner yearSpinner;
@InjectView(R.id.activity_car_selection_newcar_engine)
protected TextView engineText;
@InjectView(R.id.activity_car_selection_newcar_engine_spinner)
protected Spinner engineSpinner;
@InjectView(R.id.activity_car_selection_newcar_radio_group)
protected RadioGroup fuelTypeRadioGroup;
@InjectView(R.id.activity_car_selection_newcar_radio_group_gasoline)
protected RadioButton gasolineRadio;
@InjectView(R.id.activity_car_selection_newcar_radio_group_diesel)
protected RadioButton dieselRadio;
@Inject
protected DAOProvider daoProvider;
@Inject
protected CarPreferenceHandler carManager;
private Subscription sensorsSubscription;
private Subscription createCarSubscription;
private Scheduler.Worker mainThreadWorker = AndroidSchedulers.mainThread().createWorker();
private Set<Car> mCars = new HashSet<>();
private Set<String> mManufacturerNames = new HashSet<>();
private Map<String, Set<String>> mCarToModelMap = new ConcurrentHashMap<>();
private Map<String, Set<String>> mModelToYear = new ConcurrentHashMap<>();
private Map<Pair<String, String>, Set<String>> mModelToCCM = new ConcurrentHashMap<>();
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(
R.layout.activity_car_selection_newcar_fragment, container, false);
ButterKnife.inject(this, view);
// Get the display size in pixels
Display display = getActivity().getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
// Set the dropdown width of the spinner to half of the display pixel width.
manufacturerSpinner.setDropDownWidth(width / 2);
modelSpinner.setDropDownWidth(width / 2);
yearSpinner.setDropDownWidth(width / 2);
engineSpinner.setDropDownWidth(width / 2);
toolbar.setNavigationIcon(R.drawable.ic_close_white_24dp);
toolbar.inflateMenu(R.menu.menu_logbook_add_fueling);
toolbar.setNavigationOnClickListener(v -> closeThisFragment());
// initially we set the toolbar exp to gone
toolbar.setVisibility(View.GONE);
toolbarExp.setVisibility(View.GONE);
contentView.setVisibility(View.GONE);
downloadView.setVisibility(View.INVISIBLE);
createCarSubscription = RxToolbar.itemClicks(toolbar)
.filter(continueWhenFormIsCorrect())
.map(createCarFromForm())
.filter(continueWhenCarHasCorrectValues())
.map(checkCarAlreadyExist())
.subscribe(new Subscriber<Car>() {
@Override
public void onCompleted() {
LOG.info("onCompleted car");
}
@Override
public void onError(Throwable e) {
LOG.warn(e.getMessage(), e);
}
@Override
public void onNext(Car car) {
LOG.info("car added");
((CarSelectionUiListener) getActivity()).onCarAdded(car);
closeThisFragment();
}
});
dispatchRemoteSensors();
initFocusChangedListener();
initTextWatcher();
return view;
}
@Override
public void onResume() {
LOG.info("onResume()");
super.onResume();
ECAnimationUtils.animateShowView(getContext(), toolbar,
R.anim.translate_slide_in_top_fragment);
ECAnimationUtils.animateShowView(getContext(), toolbarExp,
R.anim.translate_slide_in_top_fragment);
ECAnimationUtils.animateShowView(getContext(), contentView,
R.anim.translate_slide_in_bottom_fragment);
}
@Override
public void onDestroy() {
LOG.info("onDestroy()");
if (sensorsSubscription != null && !sensorsSubscription.isUnsubscribed()) {
sensorsSubscription.unsubscribe();
}
if (createCarSubscription != null && !createCarSubscription.isUnsubscribed()) {
createCarSubscription.unsubscribe();
}
super.onDestroy();
}
/**
* Add car button onClick listener. When clicked, it tries to find out if the car already
* exists. If this is the case, then it adds the car to the list of selected cars. If not,
* then it selects
*/
private Func1<MenuItem, Boolean> continueWhenFormIsCorrect() {
return menuItem -> {
// First, reset the form
manufacturerText.setError(null);
modelText.setError(null);
yearText.setError(null);
engineText.setError(null);
View focusView = null;
//First check all input forms for empty strings
if (engineText.getText().length() == 0) {
engineText.setError("Cannot be empty");
focusView = engineText;
}
if (yearText.getText().length() == 0) {
yearText.setError("Cannot be empty");
focusView = yearText;
}
if (modelText.getText().length() == 0) {
modelText.setError("Cannot be empty");
focusView = modelText;
}
if (manufacturerText.getText().length() == 0) {
manufacturerText.setError("Cannot be empty");
focusView = manufacturerText;
}
// if any of the input forms contained empty values, then set the focus to the
// last one set.
if (focusView != null) {
LOG.info("Some input fields were empty");
focusView.requestFocus();
return false;
} else {
return true;
}
};
}
private <T> Func1<T, Car> createCarFromForm() {
return t -> {
// Get the values
String manufacturer = manufacturerText.getText().toString();
String model = modelText.getText().toString();
String yearString = yearText.getText().toString();
String engineString = engineText.getText().toString();
Car.FuelType fuelType = gasolineRadio.isChecked() ?
Car.FuelType.GASOLINE : Car.FuelType.DIESEL;
// create the car
return new CarImpl(manufacturer, model, fuelType,
Integer.parseInt(yearString), Integer.parseInt(engineString));
};
}
private Func1<Car, Boolean> continueWhenCarHasCorrectValues() {
return car -> {
int currentYear = Calendar.getInstance().get(Calendar.YEAR);
View focusView = null;
// Check the values of engine and year for validity.
if (car.getEngineDisplacement() < 500 || car.getEngineDisplacement() > 5000) {
engineText.setError("Invalid value");
focusView = engineText;
}
if (car.getConstructionYear() < 1990 || car.getConstructionYear() > currentYear) {
yearText.setError("Invalid value");
focusView = yearText;
}
// if tengine or year have invalid values, then request the focus.
if (focusView != null) {
focusView.requestFocus();
return false;
}
return true;
};
}
private Func1<Car, Car> checkCarAlreadyExist() {
return car -> {
String manu = car.getManufacturer();
String model = car.getModel();
String year = "" + car.getConstructionYear();
String engine = "" + car.getEngineDisplacement();
Pair<String, String> modelYear = new Pair<>(model, year);
Car selectedCar = null;
if (mManufacturerNames.contains(manu)
&& mCarToModelMap.get(manu) != null
&& mCarToModelMap.get(manu).contains(model)
&& mModelToYear.get(model) != null
&& mModelToYear.get(model).contains(year)
&& mModelToCCM.get(modelYear) != null
&& mModelToCCM.get(modelYear).contains(engine)) {
for (Car other : mCars) {
if (other.getManufacturer().equals(manu)
&& other.getModel().equals(model)
&& other.getConstructionYear() == car.getConstructionYear()
&& other.getEngineDisplacement() == car.getEngineDisplacement()
&& other.getFuelType() == car.getFuelType()) {
selectedCar = other;
break;
}
}
}
if (selectedCar == null) {
LOG.info("New Car type. Register car at server.");
carManager.registerCarAtServer(car);
return car;
} else {
LOG.info(String.format("Car already existed -> [%s]", selectedCar.getId()));
return selectedCar;
}
};
}
private void dispatchRemoteSensors() {
sensorsSubscription = daoProvider.getSensorDAO()
.getAllCarsObservable()
.onBackpressureBuffer(10000)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(new Subscriber<List<Car>>() {
@Override
public void onStart() {
LOG.info("onStart() download sensors");
downloadView.setVisibility(View.VISIBLE);
}
@Override
public void onCompleted() {
LOG.info("onCompleted(): cars successfully downloaded.");
mainThreadWorker.schedule(() -> {
// Update the manufactuerers in
updateSpinner(mManufacturerNames, manufacturerSpinner);
// Set the initial selection of the manufacturer to NO SELECTION
manufacturerSpinner.setSelection(Adapter.NO_SELECTION, true);
// Initialize the spinner.
initSpinner();
unsubscribe();
downloadView.setVisibility(View.INVISIBLE);
});
}
@Override
public void onError(Throwable e) {
LOG.error(e.getMessage(), e);
mainThreadWorker.schedule(() -> {
downloadView.setVisibility(View.INVISIBLE);
});
}
@Override
public void onNext(List<Car> cars) {
for (Car car : cars) {
if (car != null)
addCarToAutocompleteList(car);
}
}
});
}
private void initTextWatcher() {
manufacturerText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Nothing to do..
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// nothing to do..
}
@Override
public void afterTextChanged(Editable s) {
manufacturerText.setError(null);
modelText.setText("");
yearText.setText("");
engineText.setText("");
modelSpinner.setAdapter(null);
yearSpinner.setAdapter(null);
engineSpinner.setAdapter(null);
}
});
modelText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Nothing to do..
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Nothing to do..
}
@Override
public void afterTextChanged(Editable s) {
modelText.setError(null);
yearText.setText("");
engineText.setText("");
}
});
yearText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Nothing to do..
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Nothing to do..
}
@Override
public void afterTextChanged(Editable s) {
yearText.setError(null);
engineText.setText("");
}
});
engineText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Nothing to do..
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Nothing to do..
}
@Override
public void afterTextChanged(Editable s) {
engineText.setError(null);
}
});
}
private void initFocusChangedListener() {
manufacturerText.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
String manufacturer = manufacturerText.getText().toString();
updateModelViews(manufacturer);
}
});
modelText.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
String model = modelText.getText().toString();
updateYearView(model);
}
});
yearText.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
String year = yearText.getText().toString();
String model = modelText.getText().toString();
Pair<String, String> modelYear = new Pair<>(model, year);
updateEngineView(modelYear);
}
});
engineText.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
checkFuelingType();
}
});
}
private void initSpinner() {
manufacturerSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String manufacturer = parent.getItemAtPosition(position).toString();
LOG.info(String.format("manufactuererSpinner.onItemSelected(%s)", manufacturer));
// update the manufacturer textview.
manufacturerText.setText(manufacturer);
((TextView) view).setText(null);
// update the model views
updateModelViews(manufacturer);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
modelSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String model = parent.getItemAtPosition(position).toString();
LOG.info(String.format("modelSpinner.onItemSelected(%s)", model));
modelText.setText(model);
((TextView) view).setText(null);
updateYearView(model);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
yearSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String year = parent.getItemAtPosition(position).toString();
LOG.info(String.format("yearSpinner.onItemSelected(%s)", year));
yearText.setText(year);
((TextView) view).setText(null);
updateEngineView(new Pair<>(modelText.getText().toString(), year));
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
engineSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String engine = parent.getItemAtPosition(position).toString();
LOG.info(String.format("engineSpinner.onItemSelected(%s)", engine));
engineText.setText(engine);
((TextView) view).setText(null);
checkFuelingType();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
private void checkFuelingType() {
String manufacturer = manufacturerText.getText().toString();
String model = modelText.getText().toString();
String yearString = yearText.getText().toString();
String engineString = engineText.getText().toString();
Pair<String, String> modelYear = new Pair<>(model, yearString);
Car selectedCar = null;
if (mManufacturerNames.contains(manufacturer)
&& mCarToModelMap.get(manufacturer) != null
&& mCarToModelMap.get(manufacturer).contains(model)
&& mModelToYear.get(model) != null
&& mModelToYear.get(model).contains(yearString)
&& mModelToCCM.get(modelYear) != null
&& mModelToCCM.get(modelYear).contains(engineString)) {
for (Car other : mCars) {
if (other.getManufacturer() == null ||
other.getModel() == null ||
other.getConstructionYear() == 0 ||
other.getEngineDisplacement() == 0 ||
other.getFuelType() == null) {
continue;
}
if (other.getManufacturer().equals(manufacturer)
&& other.getModel().equals(model)
&& other.getConstructionYear() == Integer.parseInt(yearString)
&& other.getEngineDisplacement() == Integer.parseInt(engineString)) {
selectedCar = other;
break;
}
}
}
if (selectedCar != null &&
selectedCar.getFuelType() != null &&
selectedCar.getFuelType() == Car.FuelType.DIESEL) {
dieselRadio.setChecked(true);
} else {
gasolineRadio.setChecked(true);
}
}
private void updateManufacturerViews() {
if (!mManufacturerNames.isEmpty()) {
updateSpinner(mManufacturerNames, manufacturerSpinner);
} else {
modelSpinner.setAdapter(null);
}
}
private void updateModelViews(String manufacturer) {
if (mCarToModelMap.containsKey(manufacturer)) {
updateSpinner(mCarToModelMap.get(manufacturer), modelSpinner);
} else {
modelSpinner.setAdapter(null);
}
}
private void updateYearView(String model) {
if (mModelToYear.containsKey(model)) {
updateSpinner(mModelToYear.get(model), yearSpinner);
} else {
yearSpinner.setAdapter(null);
}
}
private void updateEngineView(Pair<String, String> model) {
if (mModelToCCM.containsKey(model)) {
updateSpinner(mModelToCCM.get(model), engineSpinner);
} else {
engineSpinner.setAdapter(null);
}
}
private void updateAutoComplete(Set<String> toSet, TextView textView) {
List<String> list = new ArrayList<>();
list.addAll(toSet);
Collections.sort(list);
// ArrayAdapter<String>
}
private void updateSpinner(Set<String> toSet, Spinner spinner) {
List<String> list = new ArrayList<>();
list.addAll(toSet);
Collections.sort(list);
ArrayAdapter<String> adapter = new ArrayAdapter<>(
getActivity(),
R.layout.activity_car_selection_newcar_spinner_item,
list.toArray(new String[list.size()]));
spinner.setAdapter(adapter);
}
/**
* Inserts the attributes of the car
*
* @param car
*/
private void addCarToAutocompleteList(Car car) {
mCars.add(car);
String manufacturer = car.getManufacturer();
String model = car.getModel();
String year = "" + car.getConstructionYear();
if (!mManufacturerNames.contains(manufacturer))
mManufacturerNames.add(manufacturer);
if (!mCarToModelMap.containsKey(manufacturer))
mCarToModelMap.put(manufacturer, new HashSet<>());
mCarToModelMap.get(manufacturer).add(model);
if (!mModelToYear.containsKey(model))
mModelToYear.put(model, new HashSet<>());
mModelToYear.get(model).add(Integer.toString(car.getConstructionYear()));
Pair<String, String> modelYearPair = new Pair<>(model, year);
if (!mModelToCCM.containsKey(modelYearPair))
mModelToCCM.put(modelYearPair, new HashSet<>());
mModelToCCM.get(modelYearPair).add(Integer.toString(car.getEngineDisplacement()));
}
public void closeThisFragment() {
// ^^
ECAnimationUtils.animateHideView(getContext(),
((CarSelectionActivity) getActivity()).overlayView, R.anim.fade_out);
ECAnimationUtils.animateHideView(getContext(), R.anim
.translate_slide_out_top_fragment, toolbar, toolbarExp);
ECAnimationUtils.animateHideView(getContext(), contentView, R.anim
.translate_slide_out_bottom, new Action0() {
@Override
public void call() {
((CarSelectionUiListener) getActivity()).onHideAddCarFragment();
}
});
}
}