Java tutorial
/** * Copyright (c) 2016 Ivan Magda * <p> * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * <p> * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * <p> * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ivanmagda.inventory.ui; import android.Manifest; import android.app.LoaderManager; import android.content.ContentValues; import android.content.CursorLoader; import android.content.DialogInterface; import android.content.Intent; import android.content.Loader; import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; import android.provider.MediaStore; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; import android.support.v4.app.NavUtils; import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.ivanmagda.inventory.R; import com.ivanmagda.inventory.model.data.ProductContract.ProductEntry; import com.ivanmagda.inventory.model.object.Product; import com.ivanmagda.inventory.util.ImageUtils; import com.ivanmagda.inventory.util.ProductUtils; import java.io.File; import static com.ivanmagda.inventory.ui.ProductEditor.EditorActivityMode.CREATE_NEW; import static com.ivanmagda.inventory.ui.ProductEditor.EditorActivityMode.EDIT; public class ProductEditor extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> { enum EditorActivityMode { EDIT, CREATE_NEW } private static final String LOG_TAG = ProductEditor.class.getSimpleName(); private static final int PRODUCT_LOADER = 1; private static final int CHOOSE_PICTURE_RESULT = 2; private static final int PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 3; /** * Helps to understand in what mode activity started. */ private EditorActivityMode mActivityMode = CREATE_NEW; /** * Uri of the current editing product or null if add one. */ private Uri mCurrentProductUri; /** * ImageView for presenting picture of the product. */ private ImageView mProductImageView; /** * EditText field to enter the products's name. */ private EditText mNameEditText; /** * EditText field to enter the products's price. */ private EditText mPriceEditText; /** * EditText field to enter the products's quantity. */ private EditText mQuantityEditText; /** * EditText field to enter the products's supplier email address. */ private EditText mSupplierEmailEditText; /** * TextView to show the current products's sale info. */ private TextView mSoldQuantityTextView; /** * TextView to show the current products's receive shipment info. */ private TextView mReceiveQuantityTextView; private boolean mProductHasChanged = false; private View.OnTouchListener mProductChangesTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { mProductHasChanged = true; return false; } }; /** * Currently editing product. * Used only when {@link ProductEditor} in the edit mode {@link #mActivityMode}. */ private Product mProduct; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_product_editor); configure(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == CHOOSE_PICTURE_RESULT) didFinishChooseImageFromGallery(resultCode, data); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == PERMISSION_REQUEST_READ_EXTERNAL_STORAGE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { startPickImageIntent(); } else { Snackbar.make(findViewById(android.R.id.content), R.string.enable_persmission_from_settings, Snackbar.LENGTH_INDEFINITE).setAction(R.string.enable, new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setData(Uri.parse("package:" + getPackageName())); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); startActivity(intent); } }).show(); } } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_product_editor, menu); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); if (mActivityMode == CREATE_NEW) { menu.findItem(R.id.action_delete).setVisible(false); menu.findItem(R.id.action_order).setVisible(false); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_save: if (saveProduct()) finish(); return true; case R.id.action_delete: showDeleteConfirmationDialog(); return true; case R.id.action_order: placeOrder(); return true; case android.R.id.home: if (!mProductHasChanged) { NavUtils.navigateUpFromSameTask(ProductEditor.this); return true; } showUnsavedChangesDialog(new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { NavUtils.navigateUpFromSameTask(ProductEditor.this); } }); return true; } return super.onOptionsItemSelected(item); } @Override public void onBackPressed() { if (!mProductHasChanged) { super.onBackPressed(); return; } showUnsavedChangesDialog(new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { finish(); } }); } /** * Private configure helper methods. */ private void configure() { mCurrentProductUri = getIntent().getData(); mActivityMode = (mCurrentProductUri == null ? CREATE_NEW : EDIT); findViews(); setListeners(); if (mActivityMode == CREATE_NEW) { setTitle(R.string.editor_activity_title_new_product); findViewById(R.id.container_track_sale).setVisibility(View.GONE); findViewById(R.id.container_receive_shipment).setVisibility(View.GONE); invalidateOptionsMenu(); } else { setTitle(R.string.editor_activity_title_edit_product); mQuantityEditText.setKeyListener(null); mQuantityEditText.setEnabled(false); getLoaderManager().initLoader(PRODUCT_LOADER, null, this); } } private void findViews() { mProductImageView = (ImageView) findViewById(R.id.product_image_view); mNameEditText = (EditText) findViewById(R.id.edit_product_name); mPriceEditText = (EditText) findViewById(R.id.edit_product_price); mQuantityEditText = (EditText) findViewById(R.id.edit_product_quantity); mSupplierEmailEditText = (EditText) findViewById(R.id.edit_product_supplier); mSoldQuantityTextView = (TextView) findViewById(R.id.sold_quantity_text_view); mReceiveQuantityTextView = (TextView) findViewById(R.id.receive_quantity_text_view); } private void setListeners() { mNameEditText.setOnTouchListener(mProductChangesTouchListener); mPriceEditText.setOnTouchListener(mProductChangesTouchListener); mQuantityEditText.setOnTouchListener(mProductChangesTouchListener); mSupplierEmailEditText.setOnTouchListener(mProductChangesTouchListener); mSoldQuantityTextView.setOnTouchListener(mProductChangesTouchListener); mReceiveQuantityTextView.setOnTouchListener(mProductChangesTouchListener); findViewById(R.id.decrement_sale_button).setOnTouchListener(mProductChangesTouchListener); findViewById(R.id.increment_sale_button).setOnTouchListener(mProductChangesTouchListener); findViewById(R.id.decrement_receive_button).setOnTouchListener(mProductChangesTouchListener); findViewById(R.id.increment_receive_button).setOnTouchListener(mProductChangesTouchListener); findViewById(R.id.container_product_picture).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mProductHasChanged = true; if (mProductImageView.getDrawable() == null) choosePictureFromGallery(); else showChangePictureConfirmationDialog(); } }); } /** * Implement LoaderManager.LoaderCallbacks<Cursor>. */ @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { return new CursorLoader(this, mCurrentProductUri, ProductEntry.PROJECTION_ALL, null, null, null); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { if (!cursor.moveToFirst()) return; mProduct = ProductUtils.fromCursor(cursor); updateUiWithProduct(mProduct); } private void updateUiWithProduct(Product product) { mNameEditText.setText(product.getName()); mPriceEditText.setText(String.valueOf(product.getPrice())); mQuantityEditText.setText(String.valueOf(product.getQuantity())); mSupplierEmailEditText.setText(product.getSupplier()); mSoldQuantityTextView.setText(String.valueOf(product.getSoldQuantity())); byte[] bytes = mProduct.getPicture(); if (bytes != null) { Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); mProductImageView.setImageBitmap(bitmap); } } @Override public void onLoaderReset(Loader<Cursor> loader) { mProduct = null; } /** * Private helper method for choosing image from the gallery. */ private void choosePictureFromGallery() { if (ContextCompat.checkSelfPermission(ProductEditor.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(ProductEditor.this, Manifest.permission.READ_EXTERNAL_STORAGE)) { Snackbar.make(findViewById(android.R.id.content), "Please Grant Permissions", Snackbar.LENGTH_INDEFINITE).setAction(R.string.enable, new View.OnClickListener() { @Override public void onClick(View v) { requestReadExternalStoragePermission(); } }).show(); } else { requestReadExternalStoragePermission(); } } else { startPickImageIntent(); } } private void startPickImageIntent() { Intent galleryIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(galleryIntent, CHOOSE_PICTURE_RESULT); } private void requestReadExternalStoragePermission() { ActivityCompat.requestPermissions(ProductEditor.this, new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, PERMISSION_REQUEST_READ_EXTERNAL_STORAGE); } private void didFinishChooseImageFromGallery(int resultCode, Intent data) { Toast failedToast = Toast.makeText(this, R.string.choose_image_failed_msg, Toast.LENGTH_LONG); try { if (resultCode == RESULT_OK && data != null) { Uri selectedImage = data.getData(); String[] projection = { MediaStore.Images.Media.DATA }; Cursor cursor = getContentResolver().query(selectedImage, projection, null, null, null); if (cursor != null) { int columnIndex = cursor.getColumnIndex(projection[0]); cursor.moveToFirst(); File imageFile = new File(cursor.getString(columnIndex)); cursor.close(); Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath()); mProductImageView.setImageBitmap(bitmap); } else { failedToast.show(); } } else { Toast.makeText(this, "You haven't picked Image", Toast.LENGTH_LONG).show(); } } catch (Exception e) { Log.e(LOG_TAG, "Failed to choose image", e); failedToast.show(); } } /** * Helper public method for tracking product sale and receive quantities. * * @param button pressed button. */ public void trackQuantityButtonPressed(View button) { int id = button.getId(); switch (id) { case R.id.decrement_sale_button: updateSaleQuantityTextView(mProduct.decrementSaleQuantity()); break; case R.id.increment_sale_button: updateSaleQuantityTextView(mProduct.incrementSaleQuantity()); break; case R.id.decrement_receive_button: updateReceiveQuantityTextView(mProduct.decrementReceiveQuantity()); break; case R.id.increment_receive_button: updateReceiveQuantityTextView(mProduct.incrementReceiveQuantity()); break; default: throw new IllegalArgumentException("Unsupported button pressed with id: " + id); } mQuantityEditText.setText(String.valueOf(mProduct.getQuantity())); } private void updateSaleQuantityTextView(int value) { mSoldQuantityTextView.setText(String.valueOf(value)); } private void updateReceiveQuantityTextView(int value) { mReceiveQuantityTextView.setText(String.valueOf(value)); } /** * Private helper methods for working with the database. */ private boolean saveProduct() { ContentValues values = readFromInputs(); if (values == null) return false; switch (mActivityMode) { case CREATE_NEW: Uri newRowUri = getContentResolver().insert(ProductEntry.CONTENT_URI, values); if (newRowUri == null) { Toast.makeText(this, R.string.editor_insert_product_failed, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, R.string.editor_insert_product_successful, Toast.LENGTH_SHORT).show(); return true; } break; case EDIT: int updatedRows = getContentResolver().update(mCurrentProductUri, values, null, null); if (updatedRows == 1) { Toast.makeText(this, R.string.editor_edit_product_successful, Toast.LENGTH_SHORT).show(); return true; } else { Toast.makeText(this, R.string.editor_edit_product_failed, Toast.LENGTH_SHORT).show(); } } return false; } private ContentValues readFromInputs() { byte[] picture = ImageUtils.bytesFromImageView(mProductImageView); if (picture == null) { showToastWithMessage(getString(R.string.choose_picture_msg)); return null; } String name = mNameEditText.getText().toString().trim(); if (TextUtils.isEmpty(name)) { showToastWithMessage(getString(R.string.enter_product_name_msg)); return null; } String priceString = mPriceEditText.getText().toString().trim(); if (TextUtils.isEmpty(priceString)) { showToastWithMessage(getString(R.string.enter_product_price_msg)); return null; } double price = Double.parseDouble(priceString); String quantityString = mQuantityEditText.getText().toString().trim(); if (TextUtils.isEmpty(quantityString)) { showToastWithMessage(getString(R.string.enter_quantity_msg)); return null; } int quantity = Integer.parseInt(quantityString); String supplier = mSupplierEmailEditText.getText().toString().trim(); if (TextUtils.isEmpty(supplier)) { showToastWithMessage(getString(R.string.enter_supplier_email_msg)); return null; } int soldQuantity; if (mActivityMode == EDIT) { assert mProduct != null; soldQuantity = mProduct.getSoldQuantity(); } else { String soldString = mSoldQuantityTextView.getText().toString(); if (TextUtils.isEmpty(soldString)) { showToastWithMessage(getString(R.string.enter_sold_quantity_msg)); return null; } soldQuantity = Integer.parseInt(soldString); } ContentValues values = new ContentValues(); values.put(ProductEntry.COLUMN_PRODUCT_NAME, name); values.put(ProductEntry.COLUMN_PRODUCT_PRICE, price); values.put(ProductEntry.COLUMN_PRODUCT_QUANTITY, quantity); values.put(ProductEntry.COLUMN_PRODUCT_SOLD_QUANTITY, soldQuantity); values.put(ProductEntry.COLUMN_PRODUCT_SUPPLIER, supplier); values.put(ProductEntry.COLUMN_PRODUCT_PICTURE, picture); return values; } /** * Perform the deletion of the product in the database. */ private void deleteProduct() { if (mActivityMode == CREATE_NEW) { Toast.makeText(this, R.string.editor_delete_product_failed, Toast.LENGTH_SHORT).show(); } else { int rowsDeleted = getContentResolver().delete(mCurrentProductUri, null, null); if (rowsDeleted == 0) Toast.makeText(this, R.string.editor_delete_product_failed, Toast.LENGTH_SHORT).show(); else Toast.makeText(this, R.string.editor_delete_product_successful, Toast.LENGTH_SHORT).show(); } finish(); } /** * Build and show confirmation dialogs. */ private void showChangePictureConfirmationDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(R.string.change_picture_dialog_msg); builder.setPositiveButton(getString(R.string.change), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { choosePictureFromGallery(); } }); builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (dialog != null) dialog.dismiss(); } }); AlertDialog alertDialog = builder.create(); alertDialog.show(); } private void showUnsavedChangesDialog(DialogInterface.OnClickListener listener) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(R.string.unsaved_changes_dialog_msg); builder.setPositiveButton(R.string.discard, listener); builder.setNegativeButton(R.string.keep_editing, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (dialog != null) dialog.dismiss(); } }); AlertDialog alertDialog = builder.create(); alertDialog.show(); } private void showDeleteConfirmationDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(R.string.delete_dialog_msg); builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { deleteProduct(); } }); builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { if (dialog != null) dialog.dismiss(); } }); AlertDialog alertDialog = builder.create(); alertDialog.show(); } /** * Helper method for placing an order with the specified supplier. */ private void placeOrder() { assert mProduct != null; if (mProduct.getReceiveQuantity() == 0) { Toast.makeText(this, R.string.place_order_failed_msg, Toast.LENGTH_LONG).show(); return; } Intent intent = new Intent(Intent.ACTION_SENDTO); intent.setData(Uri.parse("mailto:")); intent.putExtra(Intent.EXTRA_EMAIL, new String[] { mProduct.getSupplier() }); intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.place_order_subject) + mProduct.getName()); intent.putExtra(Intent.EXTRA_TEXT, generateOrderSummary()); if (intent.resolveActivity(getPackageManager()) != null) { startActivity(intent); } else { Toast.makeText(this, R.string.send_email_failed_msg, Toast.LENGTH_LONG).show(); } } private String generateOrderSummary() { return "We need more " + mProduct.getName() + " Please ship it with quantity of " + mProduct.getReceiveQuantity() + "."; } private void showToastWithMessage(String message) { Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); } }