DishDelish/dishdelish-app

View on GitHub
app/src/main/java/com/github/siela1915/bootcamp/UploadRecipe/RecipeStepAndIngredientManager.java

Summary

Maintainability
C
1 day
Test Coverage
A
90%
package com.github.siela1915.bootcamp.UploadRecipe;

import android.content.Context;
import android.graphics.Color;
import android.os.Handler;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.github.siela1915.bootcamp.AutocompleteApi.BooleanWrapper;
import com.github.siela1915.bootcamp.AutocompleteApi.IngredientAutocomplete;
import com.github.siela1915.bootcamp.R;
import com.github.siela1915.bootcamp.Recipes.Ingredient;
import com.github.siela1915.bootcamp.Recipes.Unit;
import com.google.android.material.textfield.TextInputLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;

public class RecipeStepAndIngredientManager {
    private final Context context;
    private final LinearLayout stepListLinearLayout;
    private final LinearLayout ingredientLinearLayout;
    private final LayoutInflater inflater;

    public RecipeStepAndIngredientManager(Context context, LinearLayout stepListLinearLayout, LinearLayout ingredientLinearLayout) {
        this.context = context;
        this.stepListLinearLayout = stepListLinearLayout;
        this.ingredientLinearLayout = ingredientLinearLayout;
        this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    public boolean isIngredientValid() {
        for (int i = 0; i < ingredientLinearLayout.getChildCount(); i++) {
            if (ingredientLinearLayout.getChildAt(i) instanceof ConstraintLayout) {
                ConstraintLayout step = (ConstraintLayout) ingredientLinearLayout.getChildAt(i);
                if (step.getChildAt(0) instanceof TextInputLayout && step.getChildAt(1) instanceof TextInputLayout && step.getChildAt(2) instanceof TextInputLayout) {
                    if (((TextInputLayout) step.getChildAt(2)).getEditText() == null
                            || ((TextInputLayout) step.getChildAt(1)).getEditText() == null
                            || ((TextInputLayout) step.getChildAt(1)).getEditText() == null)
                        return false;
                    String ingredientName = ((TextInputLayout) step.getChildAt(2)).getEditText().getText().toString();
                    String ingredientUnit = ((TextInputLayout) step.getChildAt(1)).getEditText().getText().toString();
                    String ingredientAmount = ((TextInputLayout) step.getChildAt(0)).getEditText().getText().toString();
                    if (!TextValidator.isTextValid(ingredientAmount) || !TextValidator.isTextValid(ingredientName) || !TextValidator.isTextValid(ingredientUnit) || !TextValidator.isNumberPositive(ingredientAmount))
                        return false;
                }
            }
        }
        return true;
    }

    public void addIngredient(Map<String, Integer> idMap, IngredientAutocomplete apiService, boolean offerable, List<String> prefill) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            final View ingredient = inflater.inflate(R.layout.recipe_ingredient_edittext, null, false);
            ImageView removeIngredient = (ImageView) ingredient.findViewById(R.id.removeIngredient);
            ImageView offerIngredient = (ImageView) ingredient.findViewById(R.id.offerIngredientItem);
            TextInputLayout ingredientsAmountLayout = ingredient.findViewById(R.id.ingredientsAmount);
            EditText ingredientsAmount = ingredientsAmountLayout.getEditText();
            TextInputLayout ingredientsUnitLayout = ingredient.findViewById(R.id.ingredientsUnit);
            EditText ingredientsUnit = ingredientsUnitLayout.getEditText();
            TextInputLayout ingredientsNameLayout = ingredient.findViewById(R.id.ingredientsName);
            EditText ingredientsName = ingredientsNameLayout.getEditText();

            addIngredientValidators(ingredientsAmount, ingredientsUnit, ingredientsName);

            removeIngredient.setOnClickListener(v -> removeIngredient(ingredient));

            if (offerable) {
                offerIngredient.setOnClickListener(v -> {
                    v.setActivated(!v.isActivated());
                    v.setBackgroundColor(v.isActivated() ? Color.GREEN : Color.RED);
                });
                offerIngredient.setBackgroundColor(Color.RED);
                offerIngredient.setVisibility(View.VISIBLE);
            }

            if (prefill != null && ingredientsAmount != null && ingredientsUnit != null && ingredientsName != null) {
                ingredientsAmount.setText(prefill.get(0));
                ingredientsUnit.setText(prefill.get(1));
                ingredientsName.setText(prefill.get(2));
            }

            ingredientLinearLayout.addView(ingredient);
            AutoCompleteTextView ingredientAutoComplete = (AutoCompleteTextView) ingredient.findViewById(R.id.ingredientAutoComplete);
            setupIngredientAutocomplete(ingredientAutoComplete, idMap, apiService);
        }
    }

    public void removeIngredient(View ingredient) {
        ingredientLinearLayout.removeView(ingredient);
    }

    public List<Ingredient> getIngredients() {
        ArrayList<Ingredient> ingredients = new ArrayList<Ingredient>();
        for (int i = 0; i < ingredientLinearLayout.getChildCount(); i++) {
            if (ingredientLinearLayout.getChildAt(i) instanceof ConstraintLayout) {
                ConstraintLayout step = (ConstraintLayout) ingredientLinearLayout.getChildAt(i);
                if (step.getChildAt(0) instanceof TextInputLayout && step.getChildAt(1) instanceof TextInputLayout && step.getChildAt(2) instanceof TextInputLayout) {
                    String ingredientName = ((TextInputLayout) step.getChildAt(2)).getEditText().getText().toString();
                    String ingredientUnit = ((TextInputLayout) step.getChildAt(1)).getEditText().getText().toString();
                    int ingredientAmount = Integer.parseInt(((TextInputLayout) step.getChildAt(0)).getEditText().getText().toString());
                    ingredients.add(new Ingredient(ingredientName, new Unit(ingredientAmount, ingredientUnit)));
                }
            }
        }
        return ingredients;
    }

    public List<Integer> getOfferable() {
        ArrayList<Integer> ingredients = new ArrayList<>();
        for (int i = 0; i < ingredientLinearLayout.getChildCount(); i++) {
            if (ingredientLinearLayout.getChildAt(i) instanceof ConstraintLayout) {
                ConstraintLayout step = (ConstraintLayout) ingredientLinearLayout.getChildAt(i);
                if (step.findViewById(R.id.offerIngredientItem).isActivated()) {
                    ingredients.add(i);
                }
            }
        }
        return ingredients;
    }

    public void addIngredientValidators(EditText ingredientsAmount, EditText ingredientsUnit, EditText ingredientsName) {
        validateEditText(ingredientsAmount,
                (textView, text) -> {
                    if (!TextValidator.isTextValid(text))
                        textView.setError(context.getResources().getString(R.string.ingredientsAmountEmptyErrorMessage));
                    else if (!TextValidator.isNumberPositive(text))
                        textView.setError(context.getResources().getString(R.string.ingredientsAmountInvalidErrorMessage));
                }
        );

        validateEditText(ingredientsUnit,
                (textView, text) -> {
                    if (!TextValidator.isTextValid(text))
                        textView.setError(context.getResources().getString(R.string.ingredientsUnitEmptyErrorMessage));
                }
        );

        validateEditText(ingredientsName,
                (textView, text) -> {
                    if (!TextValidator.isTextValid(text))
                        textView.setError(context.getResources().getString(R.string.ingredientsNameEmptyErrorMessage));
                });
    }

    public void setupIngredientAutocomplete(AutoCompleteTextView ingredientAutoComplete, Map<String, Integer> idMap, IngredientAutocomplete apiService){
        ingredientAutoComplete.setThreshold(1);

        final BooleanWrapper optionSelected = new BooleanWrapper(false);
        ingredientAutoComplete.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                // Clear the focus of the AutoCompleteTextView
                ingredientAutoComplete.clearFocus();
                optionSelected.setBool(true);
            }
        });
        ingredientAutoComplete.addTextChangedListener(new TextWatcher() {
            String prevString;
            boolean isTyping = false;
            private final Handler handler = new Handler();
            private final long DELAY = 500; // milliseconds


            //function to be called if the user stops typing
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    if(!optionSelected.getBool()) {
                        isTyping = false;
                        //send notification for stopped typing event
                        apiService.completeSearchNames(prevString, ingredientAutoComplete, idMap);
                    }
                }
            };
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                //doesn't consider defocusing and refocusing the text field as typing
                if(!s.toString().equals(prevString)) {
                    if (!isTyping) {
                        // Send notification for start typing event
                        ingredientAutoComplete.dismissDropDown();
                        isTyping = true;
                        optionSelected.setBool(false);
                    }
                    handler.removeCallbacks(runnable);
                    prevString = s.toString();
                    handler.postDelayed(runnable, DELAY); // set delay to 2 seconds
                }
            }
            @Override
            public void afterTextChanged(final Editable s) {
            }
        });
    }

    public boolean isStepValid() {
        for (int i = 0; i < stepListLinearLayout.getChildCount(); i++) {
            if (stepListLinearLayout.getChildAt(i) instanceof ConstraintLayout) {
                ConstraintLayout step = (ConstraintLayout) stepListLinearLayout.getChildAt(i);
                if (step.getChildAt(0) instanceof TextInputLayout) {
                    if (((TextInputLayout) step.getChildAt(0)).getEditText() == null || !TextValidator.isTextValid((((TextInputLayout) step.getChildAt(0)).getEditText().getText().toString())))
                        return false;
                }
            }
        }
        return true;
    }

    public void addStep() {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            final View step = inflater.inflate(R.layout.recipe_step_edittext, null, false);
            ImageView removeStep = (ImageView) step.findViewById(R.id.removeStep);
            TextInputLayout stepContent = (TextInputLayout) step.findViewById(R.id.stepContent);
            EditText stepContentEditText = stepContent.getEditText();

            stepContent.setHint("Step " + stepListLinearLayout.getChildCount());
            addStepValidators(stepContentEditText);

            removeStep.setOnClickListener(v -> removeStep(step));

            stepListLinearLayout.addView(step);
        }
    }

    private void removeStep(View step) {
        stepListLinearLayout.removeView(step);
    }

    public List<String> getSteps() {
        ArrayList<String> steps = new ArrayList<String>();
        for (int i = 0; i < stepListLinearLayout.getChildCount(); i++) {
            if (stepListLinearLayout.getChildAt(i) instanceof ConstraintLayout) {
                ConstraintLayout step = (ConstraintLayout) stepListLinearLayout.getChildAt(i);
                if (step.getChildAt(0) instanceof TextInputLayout) {
                    steps.add(((TextInputLayout) step.getChildAt(0)).getEditText().getText().toString());
                }
            }
        }
        return steps;
    }

    public void addStepValidators(EditText stepContent) {
        stepContent.addTextChangedListener(new AbstractTextValidator(stepContent) {
            @Override
            public void validate(TextView textView, String text) {
                if (!TextValidator.isTextValid(text)) textView.setError(context.getResources().getString(R.string.stepsEmptyErrorMessage));
            }
        });
    }

    private void validateEditText(TextView textView, BiConsumer<TextView, String> validator) {
        textView.addTextChangedListener(new AbstractTextValidator(textView) {
            @Override
            public void validate(TextView textView, String text) {
                validator.accept(textView, text);
            }
        });
    }
}