com.ultramegasoft.flavordex2.fragment.ViewPhotosFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.ultramegasoft.flavordex2.fragment.ViewPhotosFragment.java

Source

/*
 * The MIT License (MIT)
 * Copyright  2016 Steve Guidetti
 *
 * 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:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * 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.ultramegasoft.flavordex2.fragment;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;

import com.ultramegasoft.flavordex2.R;
import com.ultramegasoft.flavordex2.dialog.ConfirmationDialog;
import com.ultramegasoft.flavordex2.provider.Tables;
import com.ultramegasoft.flavordex2.util.EntryUtils;
import com.ultramegasoft.flavordex2.util.PhotoUtils;
import com.ultramegasoft.flavordex2.widget.PhotoHolder;

import java.lang.ref.WeakReference;
import java.util.ArrayList;

/**
 * Fragment to display the photos for a journal entry.
 *
 * @author Steve Guidetti
 */
public class ViewPhotosFragment extends AbsPhotosFragment implements LoaderManager.LoaderCallbacks<Cursor> {
    /**
     * Request codes for external Activities
     */
    private static final int REQUEST_DELETE_IMAGE = 700;
    private static final int REQUEST_LOCATE_IMAGE = 701;

    /**
     * Keys for the saved state
     */
    private static final String STATE_CURRENT_ITEM = "current_item";

    /**
     * The database ID for this entry
     */
    private long mEntryId;

    /**
     * The currently displayed photo
     */
    private int mCurrentItem = -1;

    /**
     * Views for this Fragment
     */
    private ViewPager mPager;
    private LinearLayout mNoDataLayout;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final Bundle args = getArguments();
        if (args != null) {
            mEntryId = args.getLong(ViewEntryFragment.ARG_ENTRY_ID);
        }

        if (savedInstanceState != null) {
            mCurrentItem = savedInstanceState.getInt(STATE_CURRENT_ITEM, mCurrentItem);
        }
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
        if (!isMediaReadable()) {
            return super.onCreateView(inflater, container, savedInstanceState);
        }

        final View root = inflater.inflate(R.layout.fragment_entry_photos, container, false);

        mPager = root.findViewById(R.id.pager);
        mPager.setAdapter(new PagerAdapter());

        return root;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        if (!isMediaReadable()) {
            return;
        }
        if (getPhotos().isEmpty()) {
            getLoaderManager().initLoader(0, null, this);
        } else {
            notifyDataChanged();
        }
    }

    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(STATE_CURRENT_ITEM, mCurrentItem);
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if (mPager != null) {
            mCurrentItem = mPager.getCurrentItem();
            mPager = null;
        }
        mNoDataLayout = null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        setHasOptionsMenu(false);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.view_photos_menu, menu);
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);

        final boolean showAdd = isMediaReadable();
        menu.findItem(R.id.menu_add_photo).setEnabled(showAdd).setVisible(showAdd);
        menu.findItem(R.id.menu_select_photo).setEnabled(showAdd);

        final boolean showTake = showAdd && hasCamera();
        menu.findItem(R.id.menu_take_photo).setEnabled(showTake);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == Activity.RESULT_OK) {
            switch (requestCode) {
            case REQUEST_DELETE_IMAGE:
                removePhoto(mCurrentItem);
                break;
            case REQUEST_LOCATE_IMAGE:
                if (data != null) {
                    replacePhoto(data.getData());
                }
                break;
            }
        }
    }

    @Override
    protected void onPhotoAdded(@NonNull PhotoHolder photo) {
        notifyDataChanged();
        mPager.setCurrentItem(getPhotos().size() - 1, true);

        final Context context = getContext();
        if (context != null) {
            new PhotoSaver(context, mEntryId, photo).execute();
        }
    }

    @Override
    protected void onPhotoRemoved(@NonNull PhotoHolder photo) {
        notifyDataChanged();

        final Context context = getContext();
        if (context != null) {
            new PhotoDeleter(context, photo).execute();
        }
    }

    /**
     * Show the message that there are no photos for this entry along with buttons to add one.
     */
    @SuppressWarnings("ConstantConditions")
    private void showNoDataLayout() {
        final AppCompatActivity activity = (AppCompatActivity) getActivity();
        if (mNoDataLayout == null) {
            mNoDataLayout = (LinearLayout) ((ViewStub) activity.findViewById(R.id.no_photos)).inflate();

            final Button btnTakePhoto = mNoDataLayout.findViewById(R.id.button_take_photo);
            if (hasCamera()) {
                btnTakePhoto.setOnClickListener(new View.OnClickListener() {
                    public void onClick(View v) {
                        takePhoto();
                    }
                });
            } else {
                btnTakePhoto.setEnabled(false);
            }

            final Button btnAddPhoto = mNoDataLayout.findViewById(R.id.button_add_photo);
            btnAddPhoto.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    addPhotoFromGallery();
                }
            });
        }

        mNoDataLayout.setVisibility(View.VISIBLE);
        activity.invalidateOptionsMenu();
    }

    /**
     * Show a confirmation dialog to delete the shown image.
     */
    public void confirmDeletePhoto() {
        final FragmentManager fm = getFragmentManager();
        if (fm == null || getPhotos().isEmpty()) {
            return;
        }

        mCurrentItem = mPager.getCurrentItem();
        ConfirmationDialog.showDialog(fm, this, REQUEST_DELETE_IMAGE, getString(R.string.title_remove_photo),
                getString(R.string.message_confirm_remove_photo), R.drawable.ic_delete);
    }

    /**
     * Delete the shown image.
     */
    public void deletePhoto() {
        if (getPhotos().isEmpty()) {
            return;
        }
        removePhoto(mPager.getCurrentItem());
    }

    /**
     * Locate a missing image.
     */
    public void locatePhoto() {
        final Fragment parent = getParentFragment();
        if (parent == null || getPhotos().isEmpty()) {
            return;
        }

        mCurrentItem = mPager.getCurrentItem();
        final Intent intent = PhotoUtils.getSelectPhotoIntent();
        parent.startActivityForResult(intent, REQUEST_LOCATE_IMAGE);
    }

    /**
     * Replace the currently shown image.
     *
     * @param uri The Uri for the new image
     */
    private void replacePhoto(@Nullable Uri uri) {
        final Context context = getContext();
        if (context == null || uri == null || getPhotos().isEmpty()) {
            return;
        }

        final PhotoHolder photo = getPhotos().get(mCurrentItem);
        if (photo != null) {
            photo.uri = uri;
            photo.hash = null;
            new PhotoSaver(context, mEntryId, photo).execute();
            notifyDataChanged();
        }
    }

    /**
     * Called whenever the list of photos might have been changed. This notifies the ViewPager's
     * Adapter and the ActionBar.
     */
    private void notifyDataChanged() {
        if (mPager != null) {
            final PagerAdapter adapter = (PagerAdapter) mPager.getAdapter();
            if (adapter != null) {
                adapter.notifyDataSetChanged();
            }
        }

        if (!getPhotos().isEmpty()) {
            if (mNoDataLayout != null) {
                mNoDataLayout.setVisibility(View.GONE);
            }
        } else {
            showNoDataLayout();
        }

        final AppCompatActivity activity = (AppCompatActivity) getActivity();
        if (activity != null) {
            activity.invalidateOptionsMenu();
        }
    }

    @SuppressWarnings("ConstantConditions")
    @NonNull
    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        final Context context = getContext();
        if (context == null) {
            return null;
        }

        final Uri uri = Uri.withAppendedPath(Tables.Entries.CONTENT_ID_URI_BASE, mEntryId + "/photos");
        final String[] projection = new String[] { Tables.Photos._ID, Tables.Photos.HASH, Tables.Photos.PATH,
                Tables.Photos.POS };
        final String where = Tables.Photos.PATH + " NOT NULL";
        final String order = Tables.Photos.POS + " ASC";
        return new CursorLoader(context, uri, projection, where, null, order);
    }

    @Override
    public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) {
        final ArrayList<PhotoHolder> photos = getPhotos();
        photos.clear();
        long id;
        String hash;
        String path;
        int pos;
        Uri uri;
        while (data.moveToNext()) {
            id = data.getLong(data.getColumnIndex(Tables.Photos._ID));
            hash = data.getString(data.getColumnIndex(Tables.Photos.HASH));
            path = data.getString(data.getColumnIndex(Tables.Photos.PATH));
            pos = data.getInt(data.getColumnIndex(Tables.Photos.POS));
            uri = PhotoUtils.parsePath(path);
            if (uri != null) {
                photos.add(new PhotoHolder(id, hash, uri, pos));
            }
        }

        notifyDataChanged();

        getLoaderManager().destroyLoader(0);
    }

    @Override
    public void onLoaderReset(@NonNull Loader<Cursor> loader) {
    }

    /**
     * Adapter for the ViewPager.
     */
    private class PagerAdapter extends FragmentStatePagerAdapter {
        /**
         * The data backing the Adapter
         */
        @NonNull
        private final ArrayList<PhotoHolder> mData;

        PagerAdapter() {
            super(getChildFragmentManager());
            mData = getPhotos();
        }

        @Override
        public Fragment getItem(int position) {
            final Bundle args = new Bundle();
            args.putParcelable(PhotoFragment.ARG_URI, mData.get(position).uri);
            return instantiate(getContext(), PhotoFragment.class.getName(), args);
        }

        @Override
        public int getCount() {
            return mData.size();
        }

        @Override
        @SuppressWarnings("MethodDoesntCallSuperMethod")
        public int getItemPosition(@NonNull Object object) {
            return POSITION_NONE;
        }
    }

    /**
     * Task to insert a photo into the database in the background.
     */
    private static class PhotoSaver extends AsyncTask<Void, Void, Boolean> {
        /**
         * The Context reference
         */
        @NonNull
        private final WeakReference<Context> mContext;

        /**
         * The entry ID to assign the photo to
         */
        private final long mEntryId;

        /**
         * The photo to save
         */
        @NonNull
        private final PhotoHolder mPhoto;

        /**
         * @param context The Context
         * @param entryId The entry ID
         * @param photo   The photo to save
         */
        PhotoSaver(@NonNull Context context, long entryId, @NonNull PhotoHolder photo) {
            mContext = new WeakReference<>(context.getApplicationContext());
            mEntryId = entryId;
            mPhoto = photo;
        }

        @Override
        protected Boolean doInBackground(Void... params) {
            final Context context = mContext.get();
            if (context == null) {
                return false;
            }

            final ContentResolver cr = context.getContentResolver();
            Uri uri = PhotoUtils.getFileUri(cr, mPhoto.uri);
            if (uri == null) {
                return false;
            }
            mPhoto.uri = uri;

            if (mPhoto.hash == null) {
                mPhoto.hash = PhotoUtils.getMD5Hash(cr, mPhoto.uri);
            }

            final ContentValues values = new ContentValues();
            values.put(Tables.Photos.HASH, mPhoto.hash);
            values.put(Tables.Photos.PATH, mPhoto.uri.getLastPathSegment());
            values.put(Tables.Photos.POS, mPhoto.pos);

            if (mPhoto.id > 0) {
                uri = ContentUris.withAppendedId(Tables.Photos.CONTENT_ID_URI_BASE, mPhoto.id);
                if (cr.update(uri, values, null, null) < 1) {
                    return false;
                }
            } else {
                uri = Uri.withAppendedPath(Tables.Entries.CONTENT_ID_URI_BASE, mEntryId + "/photos");
                uri = cr.insert(uri, values);
                if (uri == null) {
                    return false;
                }
                mPhoto.id = Long.valueOf(uri.getLastPathSegment());
            }

            PhotoUtils.deleteThumb(context, mEntryId);

            return true;
        }

        @Override
        protected void onPostExecute(Boolean result) {
            super.onPostExecute(result);

            if (!result) {
                final Context context = mContext.get();
                if (context != null) {
                    Toast.makeText(context, R.string.error_insert_photo, Toast.LENGTH_LONG).show();
                }
            }
        }
    }

    /**
     * Task to delete a photo from the database in the background.
     */
    private static class PhotoDeleter extends AsyncTask<Void, Void, Void> {
        /**
         * The Context reference
         */
        @NonNull
        private final WeakReference<Context> mContext;

        /**
         * The photo to delete
         */
        @NonNull
        private final PhotoHolder mPhoto;

        /**
         * @param context The Context
         * @param photo   The photo to delete
         */
        PhotoDeleter(@NonNull Context context, @NonNull PhotoHolder photo) {
            mContext = new WeakReference<>(context.getApplicationContext());
            mPhoto = photo;
        }

        @Override
        protected Void doInBackground(Void... params) {
            final Context context = mContext.get();
            if (context == null) {
                return null;
            }

            EntryUtils.deletePhoto(context, mPhoto.id);

            return null;
        }
    }
}