Java tutorial
/* * Copyright (C) 2012 Davy Leggieri * * This program 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 2 * of the License, or (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package com.bydavy.card.receipts.fragments; import android.app.Activity; import android.content.ContentValues; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.util.LruCache; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.ImageButton; import android.widget.TextView; import com.bydavy.card.receipts.R; import com.bydavy.card.receipts.activities.PreviewPictureActivity; import com.bydavy.card.receipts.fragments.PicturePickerDialogFragment.OnPictureChoseListener; import com.bydavy.card.receipts.providers.Receipts; import com.bydavy.card.receipts.providers.access.ReceiptProviderAccess; import com.bydavy.card.receipts.ui.BitmapWorkerTask; import com.bydavy.card.receipts.ui.ReceiptThumbnailCache; import com.bydavy.card.receipts.utils.CurrencyFormat; import com.bydavy.card.receipts.utils.LogHelper; import com.bydavy.card.receipts.utils.LogHelper.ErrorID; import com.bydavy.card.receipts.utils.MyDateFormat; public class ReceiptFragment extends StateSherlockFragment implements OnCheckedChangeListener, OnPictureChoseListener { /** Change first statement of if condition to enable class debug **/ private static final boolean DEBUG = LogHelper.DEBUG ? false : LogHelper.DEFAULT_DEBUG; private static final String DEBUG_PREFIX = ReceiptFragment.class.getName(); private static final String INSERT_PICTURE_DIALOG_FRAGMENT_TAG = "insertPictureDialog"; private static final String ARG_RECEIPT_ID = "receiptId"; private static final int CURSOR_LOADER_RECEIPT = 0; private MyDateFormat mDateFormat; private CurrencyFormat mCurrencyFormat; private View mLoadingContainer; private View mReceiptContainer; private View mNoReceiptContainer; private TextView mViewShop; private TextView mViewDate; private TextView mViewNote; private TextView mViewTotal; private CheckBox mViewVerified; private ImageButton mViewPicture; // Data private String mPicturePath; private BitmapWorkerTask mViewPictureWorker; private final MyReceiptLoader mLoaderCallBack = new MyReceiptLoader(); public static ReceiptFragment newInstance(long receiptId) { if (receiptId <= 0) { throw new IllegalArgumentException("receipt id must be > 0"); } final ReceiptFragment f = new ReceiptFragment(); final Bundle args = new Bundle(); args.putLong(ARG_RECEIPT_ID, receiptId); f.setArguments(args); return f; } @Override public void onAttach(Activity activity) { super.onAttach(activity); mDateFormat = MyDateFormat.getDateFormat(activity); mCurrencyFormat = CurrencyFormat.getCurrencyFormat(activity); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View v = inflater.inflate(R.layout.fragment_receipt, null); // Frames mLoadingContainer = v.findViewById(R.id.loading_container); // Contents of mReceiptFrame mNoReceiptContainer = v.findViewById(R.id.fragment_receipt__no_receipt_stub); mReceiptContainer = v.findViewById(R.id.fragment_receipt__receipt); // Contents of mReceiptContainer mViewShop = (TextView) mReceiptContainer.findViewById(R.id.shop); mViewDate = (TextView) mReceiptContainer.findViewById(R.id.date); mViewNote = (TextView) mReceiptContainer.findViewById(R.id.note); mViewTotal = (TextView) mReceiptContainer.findViewById(R.id.total); mViewVerified = (CheckBox) mReceiptContainer.findViewById(R.id.verified); mViewPicture = (ImageButton) mReceiptContainer.findViewById(R.id.picture); mViewPicture.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (TextUtils.isEmpty(mPicturePath)) { showInsertPictureDialog(); } else { openPreviewActivity(); } } }); // Only display the loading screen setLoadingScreenVisible(true); return v; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // Load the receipt in a dedicated thread final Bundle b = mLoaderCallBack.createBundle(getReceiptId()); getLoaderManager().initLoader(CURSOR_LOADER_RECEIPT, b, mLoaderCallBack); } /* * Retrieve the receipt id currently displayed. * * This method is thread safe and can be invoked right after fragments * instantiation * * @return shown receipt id or -1 if no receipt id was set */ public long getReceiptId() { final Bundle args = getArguments(); if (args != null) { return args.getLong(ARG_RECEIPT_ID); } return -1; } /* * Must be executed by the UIThread */ private void updateView(Cursor c) { final boolean hasReceipt = ((c != null) && c.moveToFirst()); // Update receipt container only if has a receipt to display // Otherwise this view is GONE if (hasReceipt) { final int shopColumnIndex = c.getColumnIndex(Receipts.COLUMN_NAME_SHOP); final int noteColumnIndex = c.getColumnIndex(Receipts.COLUMN_NAME_NOTE); final int totalColumnIndex = c.getColumnIndex(Receipts.COLUMN_NAME_TOTAL); final int dateColumnIndex = c.getColumnIndex(Receipts.COLUMN_NAME_DATE); final int verifiedColumnIndex = c.getColumnIndex(Receipts.COLUMN_NAME_VERIFIED); final int picturePathColumnIndex = c.getColumnIndex(Receipts.COLUMN_NAME_PICTURE_PATH); mViewShop.setText(c.getString(shopColumnIndex)); mViewNote.setText(c.getString(noteColumnIndex)); mViewTotal.setText(mCurrencyFormat.format(c.getFloat(totalColumnIndex))); mViewDate.setText(mDateFormat.format(c.getInt(dateColumnIndex))); mViewVerified.setOnCheckedChangeListener(null); mViewVerified.setChecked(c.getInt(verifiedColumnIndex) == 1); mViewVerified.setOnCheckedChangeListener(this); // Cancel picture task if (mViewPictureWorker != null) { mViewPictureWorker.cancel(true); } mPicturePath = c.getString(picturePathColumnIndex); updateThumbnail(mPicturePath); } setReceiptViewsVisibility(hasReceipt); setLoadingScreenVisible(false); } private void updateThumbnail(String picturePath) { if (TextUtils.isEmpty(mPicturePath)) { mViewPicture.setImageDrawable(getResources().getDrawable(R.drawable.ic_menu_camera)); return; } final LruCache<String, Bitmap> cache = ReceiptThumbnailCache.mCache; Bitmap bitmap; synchronized (cache) { bitmap = cache.get(mPicturePath); } if (bitmap != null) { mViewPicture.setImageBitmap(bitmap); } else { mViewPictureWorker = new BitmapWorkerTask(mViewPicture, mViewPicture.getLayoutParams().width, mViewPicture.getLayoutParams().height, ReceiptThumbnailCache.mCache) { @Override protected Bitmap doInBackground(String... params) { Bitmap bitmap = super.doInBackground(params); // If picture not found remove it from DB if (bitmap == null) { // TODO log this event (user removed picture from // another app) ReceiptProviderAccess.updatePicture(getActivity(), getReceiptId(), null, mPicturePath, false); } return bitmap; } }; mViewPictureWorker.execute(mPicturePath); } } /* * Must be executed by the UIThread */ private void setReceiptViewsVisibility(boolean isVisible) { if (isVisible) { mReceiptContainer.setVisibility(View.VISIBLE); mNoReceiptContainer.setVisibility(View.GONE); } else { mReceiptContainer.setVisibility(View.GONE); mNoReceiptContainer.setVisibility(View.VISIBLE); } } /* * Must be executed by the UIThread */ private void setLoadingScreenVisible(boolean isVisible) { if (isVisible) { mLoadingContainer.setVisibility(View.VISIBLE); mReceiptContainer.setVisibility(View.GONE); } else { mLoadingContainer.setVisibility(View.GONE); mReceiptContainer.setVisibility(View.VISIBLE); } } private void showInsertPictureDialog() { final FragmentTransaction ft = getFragmentManager().beginTransaction(); final Fragment prev = getFragmentManager().findFragmentByTag(INSERT_PICTURE_DIALOG_FRAGMENT_TAG); if (prev != null) { ft.remove(prev); } ft.addToBackStack(null); final PicturePickerDialogFragment dialog = PicturePickerDialogFragment.newInstance(); dialog.show(ft, INSERT_PICTURE_DIALOG_FRAGMENT_TAG); } private void openPreviewActivity() { final Intent intent = new Intent(getActivity(), PreviewPictureActivity.class); intent.putExtra(PreviewPictureActivity.INTENT_BUNDLE_RECEIPT_ID, getReceiptId()); startActivity(intent); } /* * Called when the user click on "validate" checkbox of a receipt * * @see * android.widget.CompoundButton.OnCheckedChangeListener#onCheckedChanged * (android.widget.CompoundButton, boolean) */ @Override public void onCheckedChanged(CompoundButton buttonView, final boolean isChecked) { final long receiptId = getReceiptId(); // TODO execute it outside the UIThread ?! On GNexus takes 5-15ms. // A simple Thread is a bad implementation, a thread with a queue that // execute sequentially tasks is a better implementation final ContentValues values = new ContentValues(); values.put(Receipts.COLUMN_NAME_VERIFIED, isChecked); final Uri uri = Uri.withAppendedPath(Receipts.CONTENT_URI, String.valueOf(receiptId)); final int rows = getActivity().getContentResolver().update(uri, values, null, null); if (rows != 1) { LogHelper.e(ErrorID.CONTENT_PROVIDER, "Impossible to update receipt " + receiptId, null); } } private final class MyReceiptLoader implements LoaderCallbacks<Cursor> { private static final String BUNDLE_RECEIPT_ID = "receiptId"; /* Not possible to create a static method =0/ */ public Bundle createBundle(long receiptId) { final Bundle bundle = new Bundle(); bundle.putLong(BUNDLE_RECEIPT_ID, receiptId); return bundle; } /* Dedicated Thread */ @Override public Loader<Cursor> onCreateLoader(int id, Bundle bundle) { switch (id) { case CURSOR_LOADER_RECEIPT: { if (bundle == null) { throw new IllegalArgumentException("Must set a bundle"); } final long receiptId = bundle.getLong(BUNDLE_RECEIPT_ID); if (receiptId <= 0L) { throw new IllegalArgumentException("Must set " + BUNDLE_RECEIPT_ID); } final Uri uriSingleReceipt = Uri.withAppendedPath(Receipts.CONTENT_URI, String.valueOf(receiptId)); return new CursorLoader(getActivity(), uriSingleReceipt, null, null, null, null); } default: throw new IllegalArgumentException("The cursor loader " + id + " doesn't exist"); } } /* UI Thread */ @Override public void onLoadFinished(Loader<Cursor> cl, final Cursor cursor) { updateView(cursor); if (getState() != StateFragmentListener.STATE_COMPLETED) { setState(StateFragmentListener.STATE_COMPLETED); } } /* UI Thread */ @Override public void onLoaderReset(Loader<Cursor> cl) { } } @Override public void onPictureChose(String picturePath, int from) { if ((from != OnPictureChoseListener.GALLERY) && (from != OnPictureChoseListener.TEMPORARY)) { LogHelper.e(ErrorID.IMPLEMENTATION, "A new value was added to " + OnPictureChoseListener.class.getName() + " from, and it's not handled.", null); return; } mPicturePath = ReceiptProviderAccess.updatePicture(getActivity(), getReceiptId(), picturePath, mPicturePath, from != OnPictureChoseListener.GALLERY); } }