EditRecipeActivity.java

/*
 * Copyright 2016 the Cook-E development team
 *
 * This file is part of Cook-E.
 *
 * Cook-E 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.
 *
 * Cook-E 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with Cook-E.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.cook_e.cook_e;

import android.app.AlertDialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.DialogInterface;
import android.content.Intent;
import android.databinding.ObservableArrayList;
import android.databinding.ObservableList;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.TextView;

import org.cook_e.cook_e.ui.RecipeEditDialogFragment;
import org.cook_e.cook_e.ui.StepDialogFragment;
import org.cook_e.data.Recipe;
import org.cook_e.data.Step;

import java.sql.SQLException;

/**
 * Created by Tyler on 2/4/2016.
 *
 * This is the activity for editing a particular recipe.
 */
public class EditRecipeActivity extends AppCompatActivity
        implements StepDialogFragment.StepEditListener, RecipeEditDialogFragment.RecipeEditListener {

    /**
     * Intent extra that provides the activity to edit
     */
    public static final String EXTRA_RECIPE = EditRecipeActivity.class.getName() + ".RECIPE";

    /**
     * Key used in bundles when saving and restoring the state of {@link #mRecipe}
     */
    private static final String KEY_RECIPE = EditRecipeActivity.class.getName() + ".RECIPE";
    /**
     * Key used in bundles when saving and restoring the state of {@link #mStepEditIndex}
     */
    private static final String KEY_STEP_EDIT_INDEX = EditRecipeActivity.class.getName() + ".STEP_EDIT_INDEX";


    /**
     * The recipe being edited
     */
    private Recipe mRecipe;

    /**
     * The steps of the recipe being edited
     */
    private ObservableArrayList<Step> mSteps;

    /**
     * The index in {@link #mSteps} of the step currently being edited, or -1 if no step is being
     * edited
     */
    private int mStepEditIndex;
    private TextView mAuthorView;

    /*
     * Sets up the main view to edit recipes.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_edit_recipe);

        if (savedInstanceState == null) {
            // Get recipe from intent
            mRecipe = unpackRecipe();
            mStepEditIndex = -1;
        } else {
            // Get recipe from saved state
            mRecipe = savedInstanceState.getParcelable(KEY_RECIPE);
            if (mRecipe == null) {
                throw new IllegalStateException("No saved recipe in savedInstanceState");
            }
            mStepEditIndex = savedInstanceState.getInt(KEY_STEP_EDIT_INDEX);
        }
        mSteps = new ObservableArrayList<>();
        mSteps.addAll(mRecipe.getSteps());

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        final ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setTitle(mRecipe.getTitle());
        }

        // Set up recipe description
        mAuthorView = (TextView) findViewById(R.id.recipe_author);
        mAuthorView.setText(mRecipe.getAuthor());
        // Recipe image
        final ImageButton imageView = (ImageButton) findViewById(R.id.recipe_image_button);
        imageView.setImageBitmap(mRecipe.getImage());

        final StepListAdapter stepsAdapter = new StepListAdapter(this, mSteps);
        ListView stepsList = (ListView) findViewById(R.id.recipeSteps);
        stepsList.setAdapter(stepsAdapter);

        (findViewById(R.id.step_add_button)).setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        // Set the index at which to insert the step
                        mStepEditIndex = mSteps.size();
                        // Show a dialog to create a step
                        final FragmentTransaction transaction = getFragmentManager().beginTransaction();
                        final Fragment previous = getFragmentManager().findFragmentByTag("dialog");
                        if (previous != null) {
                            transaction.remove(previous);
                        }
                        transaction.addToBackStack(null);
                        final StepDialogFragment dialog = StepDialogFragment.newInstance(null);
                        dialog.show(transaction, "dialog");
                    }
                }
        );

        // Begin editing a step when the user clicks on it
        stepsAdapter.setStepClickListener(new StepListAdapter.StepClickListener() {
            @Override
            public void onStepClicked(Step step, int index) {
                mStepEditIndex = index;
                // Show a dialog to edit the step
                final FragmentTransaction transaction = getFragmentManager().beginTransaction();
                final Fragment previous = getFragmentManager().findFragmentByTag("dialog");
                if (previous != null) {
                    transaction.remove(previous);
                }
                transaction.addToBackStack(null);
                final StepDialogFragment dialog = StepDialogFragment.newInstance(step);
                dialog.show(transaction, "dialog");
            }
        });

        // Update recipe when step list changes
        mSteps.addOnListChangedCallback(new ObservableList.OnListChangedCallback() {
            @Override
            public void onChanged(ObservableList sender) {
                updateRecipeSteps();
            }

            @Override
            public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) {
                updateRecipeSteps();
            }

            @Override
            public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) {
                updateRecipeSteps();
            }

            @Override
            public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition, int itemCount) {
                updateRecipeSteps();
            }

            @Override
            public void onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount) {
                updateRecipeSteps();
            }
        });
    }

    @Override
    public void stepEditingFinished(@NonNull Step step) {
        // User finished editing a step
        // Put it in the list
        if (mStepEditIndex == -1) {
            throw new IllegalStateException("mStepEditIndex is -1");
        }
        if (mStepEditIndex > mSteps.size()) {
            throw new IllegalStateException("mStepEditIndex is too large");
        }

        if (mStepEditIndex == mSteps.size()) {
            mSteps.add(step);
        } else {
            mSteps.set(mStepEditIndex, step);
        }
        mStepEditIndex = -1;
    }


    @Override
    public void recipeEditingFinished(@NonNull String newTitle, @NonNull String newAuthor) {
        mRecipe.setTitle(newTitle);
        mRecipe.setAuthor(newAuthor);
        // Update UI
        final ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setTitle(newTitle);
        }
        mAuthorView.setText(newAuthor);
        saveRecipe();
    }

    /**
     * Updates the steps in {@link #mRecipe} to equal {@link #mSteps}, and saves mRecipe
     */
    private void updateRecipeSteps() {
        mRecipe.setSteps(mSteps);
        saveRecipe();
    }

    /**
     * Tries to save the recipe
     */
    private void saveRecipe() {
        try {
            App.getAccessor().persistRecipe(mRecipe);
        } catch (SQLException e) {
            new AlertDialog.Builder(this)
                    .setTitle("Failed to save")
                    .setMessage(e.getLocalizedMessage())
                    .show();
        }
    }

    /**
     * Tries to delete the recipe, then exits
     */
    private void deleteRecipe() {
        try {
            App.getAccessor().deleteRecipe(mRecipe);
            finish();
        } catch (SQLException e) {
            new AlertDialog.Builder(this)
                    .setTitle("Failed to delete recipe")
                    .setMessage(e.getLocalizedMessage())
                    .show();
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // Save the current recipe and edit state
        outState.putParcelable(KEY_RECIPE, mRecipe);
        outState.putInt(KEY_STEP_EDIT_INDEX, mStepEditIndex);
    }

    /**
     * Accesses the recipe to be edited
     *
     * @return a recipe from the intent that started this activity
     */
    private Recipe unpackRecipe() {
        final Intent intent = getIntent();
        final Recipe recipe = intent.getParcelableExtra(EXTRA_RECIPE);
        if (recipe == null) {
            throw new IllegalStateException("No activity extra provided in intent");
        }
        return recipe;
    }


    /*
     * This is a workaround for inconsistent behavior.
     *
     * Pressing the system back button or calling finish() returns a result to the parent activity,
     * as expected. However, the default action when the up button is pressed does not send a result
     * to the parent. This override ensures that a result is sent when the action bar up button is
     * pressed.
     */
    @Override
    public boolean onSupportNavigateUp() {
        finish();
        return true;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.edit_recipe, menu);

        final MenuItem editItem = menu.findItem(R.id.item_edit_recipe);
        editItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                // Open the RecipeEditDialogFragment
                // Show a dialog to create a step
                final FragmentTransaction transaction = getFragmentManager().beginTransaction();
                final Fragment previous = getFragmentManager().findFragmentByTag("dialog");
                if (previous != null) {
                    transaction.remove(previous);
                }
                transaction.addToBackStack(null);
                final DialogFragment dialog = RecipeEditDialogFragment.newInstance(mRecipe);
                dialog.show(transaction, "dialog");

                return true;
            }
        });

        final MenuItem deleteItem = menu.findItem(R.id.item_delete_recipe);
        deleteItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                // Show a confirmation dialog
                new AlertDialog.Builder(EditRecipeActivity.this)
                        .setTitle(R.string.question_delete_recipe)
                        .setPositiveButton(android.R.string.ok,
                                new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        deleteRecipe();
                                    }
                                })
                        .setNegativeButton(android.R.string.cancel, null)
                        .show();

                return true;
            }
        });

        return true;
    }
}