com.ratebeer.android.gui.fragments.BeerViewFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.ratebeer.android.gui.fragments.BeerViewFragment.java

Source

/*
This file is part of RateBeer For Android.
    
RateBeer for Android 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.
    
RateBeer for Android 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 RateBeer for Android.  If not, see 
<http://www.gnu.org/licenses/>.
 */
package com.ratebeer.android.gui.fragments;

import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.provider.MediaStore;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.text.Html;
import android.text.method.ScrollingMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RatingBar;
import android.widget.TextView;

import com.commonsware.cwac.merge.MergeAdapter;
import com.googlecode.androidannotations.annotations.AfterViews;
import com.googlecode.androidannotations.annotations.EFragment;
import com.googlecode.androidannotations.annotations.FragmentArg;
import com.googlecode.androidannotations.annotations.InstanceState;
import com.googlecode.androidannotations.annotations.OptionsItem;
import com.googlecode.androidannotations.annotations.OptionsMenu;
import com.googlecode.androidannotations.annotations.ViewById;
import com.ratebeer.android.R;
import com.ratebeer.android.api.ApiMethod;
import com.ratebeer.android.api.CommandFailureResult;
import com.ratebeer.android.api.CommandSuccessResult;
import com.ratebeer.android.api.UserSettings;
import com.ratebeer.android.api.command.GetBeerAvailabilityCommand;
import com.ratebeer.android.api.command.GetBeerDetailsCommand;
import com.ratebeer.android.api.command.GetBeerDetailsCommand.BeerDetails;
import com.ratebeer.android.api.command.GetBeerPhotoAvailableCommand;
import com.ratebeer.android.api.command.GetRatingsCommand;
import com.ratebeer.android.api.command.GetRatingsCommand.BeerRating;
import com.ratebeer.android.api.command.GetUserTicksCommand;
import com.ratebeer.android.api.command.GetUserTicksCommand.UserTick;
import com.ratebeer.android.api.command.ImageUrls;
import com.ratebeer.android.api.command.PostRatingCommand;
import com.ratebeer.android.api.command.SearchPlacesCommand.PlaceSearchResult;
import com.ratebeer.android.api.command.Style;
import com.ratebeer.android.app.RateBeerForAndroid;
import com.ratebeer.android.gui.components.PosterService;
import com.ratebeer.android.gui.components.RateBeerFragment;
import com.ratebeer.android.gui.components.helpers.ActivityUtil;
import com.ratebeer.android.gui.components.helpers.ArrayAdapter;
import com.ratebeer.android.gui.components.helpers.NfcHelper.NdefUriProvider;
import com.ratebeer.android.gui.fragments.AddToCellarFragment.CellarType;
import com.viewpagerindicator.TabPageIndicator;

@EFragment(R.layout.fragment_beerview)
@OptionsMenu({ R.menu.refresh, R.menu.share })
public class BeerViewFragment extends RateBeerFragment implements NdefUriProvider {

    private static final String DECIMAL_FORMATTER = "%.1f";
    private static final int UNKNOWN_RATINGS_COUNT = -1;
    private static final int ACTIVITY_CAMERA = 0;
    private static final int ACTIVITY_PICKPHOTO = 1;

    @FragmentArg
    @InstanceState
    protected int beerId;
    @FragmentArg
    @InstanceState
    protected String beerName = null;
    @FragmentArg
    @InstanceState
    protected int ratingsCount = UNKNOWN_RATINGS_COUNT;
    @InstanceState
    protected BeerDetails details = null;
    @InstanceState
    protected Boolean photoIsAvailable = null;
    @InstanceState
    protected ArrayList<BeerRating> ownRatings = null;
    @InstanceState
    protected ArrayList<UserTick> ownTicks = null;
    @InstanceState
    protected ArrayList<BeerRating> recentRatings = new ArrayList<BeerRating>();
    @InstanceState
    protected ArrayList<PlaceSearchResult> availability = new ArrayList<PlaceSearchResult>();

    @ViewById
    protected ViewPager pager;
    @ViewById
    protected TabPageIndicator titles;
    private View scoreCard;
    @ViewById(R.id.name)
    protected TextView nameText;
    private TextView noscoreyetText, scoreText, stylepctlText, ratingsText;
    @ViewById(R.id.description)
    protected TextView descriptionText;
    @ViewById(R.id.abvstyle)
    protected Button abvstyleButton;
    @ViewById(R.id.brewername)
    protected Button brewernameButton;
    private Button rateThisButton, drinkingThisButton, addAvailabilityButton, havethisButton, wantthisButton,
            uploadphotoButton;
    private View ownratingRow, ownratinglabel, ticklabel, otherratingslabel;
    private TextView ownratingTotal, ownratingAroma, ownratingAppearance, ownratingTaste, ownratingPalate,
            ownratingOverall, ownratingUsername, ownratingComments;
    private RatingBar tickBar;
    private ListView availabilityView;
    private TextView availabilityEmpty;
    private BeerRatingsAdapter recentRatingsAdapter;
    @ViewById(R.id.image)
    protected ImageView imageView;
    private DateFormat displayDateFormat;

    public BeerViewFragment() {
    }

    @AfterViews
    public void init() {

        displayDateFormat = android.text.format.DateFormat.getDateFormat(getActivity());
        pager.setAdapter(new BeerPagerAdapter());
        titles.setViewPager(pager);

        if (details != null) {
            publishDetails(details);
            publishPhotoAvailability(photoIsAvailable);
            refreshImage(); // Note: always requested, although the image is cached
            publishOwnRating(ownRatings);
            publishOwnTick(ownTicks);
            setRatings(recentRatings);
            setAvailability(availability);
        } else {
            refreshDetails();
            refreshPhotoAvailability();
            refreshImage();
            refreshOwnRating();
            refreshOwnTick();
            refreshRatings();
            refreshAvailability();
        }

    }

    @OptionsItem(R.id.menu_refresh)
    protected void onRefresh() {
        refreshDetails();
        refreshPhotoAvailability();
        refreshImage();
        refreshOwnRating();
        refreshOwnTick();
        refreshRatings();
        refreshAvailability();
    }

    @OptionsItem(R.id.menu_share)
    protected void onShare() {
        // Start a share intent for this beer
        Intent s = new Intent(Intent.ACTION_SEND);
        s.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
        s.setType("text/plain");
        s.putExtra(Intent.EXTRA_TEXT, getString(R.string.details_share, beerName, beerId));
        startActivity(Intent.createChooser(s, getString(R.string.details_sharebeer)));
    }

    private void refreshImage() {
        RateBeerForAndroid.getImageCache(getActivity()).displayImage(ImageUrls.getBeerPhotoUrl(beerId), imageView);
        imageView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                onShowFullScreenPhoto();
            }
        });
    }

    private void refreshDetails() {
        execute(new GetBeerDetailsCommand(getUser(), beerId));
    }

    private void refreshPhotoAvailability() {
        execute(new GetBeerPhotoAvailableCommand(getUser(), beerId));
    }

    private void refreshOwnRating() {
        if (getUser() != null) {
            execute(new GetRatingsCommand(getUser(), beerId, getUser().getUserID()));
        }
    }

    private void refreshOwnTick() {
        if (getUser() != null) {
            // TODO: Unfortunately we have to retrieve all the user's ticks, since the RB API is limited...
            execute(new GetUserTicksCommand(getUser()));
        }
    }

    private void refreshRatings() {
        execute(new GetRatingsCommand(getUser(), beerId));
    }

    private void refreshAvailability() {
        execute(new GetBeerAvailabilityCommand(getUser(), beerId));
        availabilityEmpty.setText(R.string.details_noavailability);
    }

    protected void onRateBeerClick() {
        // Get the original rating if we already rated this beer
        if (ownRatings != null && ownRatings.size() > 0) {
            BeerRating ownRating = ownRatings.get(0);
            // Start new rating fragment, with pre-populated fields
            SimpleDateFormat df = new SimpleDateFormat("M/d/yyyy h:mm:ss a", Locale.US);
            load(RateFragment_.buildFromConcrete(beerName, beerId, ownRating.ratingId,
                    df.format(ownRating.timeEntered), ownRating.appearance, ownRating.aroma, ownRating.flavor,
                    ownRating.mouthfeel, ownRating.overall, ownRating.comments));
        } else {
            // Start new rating fragment
            load(RateFragment_.buildFromBeer(beerName, beerId));
        }
    }

    protected void onTickBarUpdated(float rating) {
        int newRating = (int) rating;
        if (rating <= 0.1) {
            // Force everything below 0.1 (since the user might not have the finger all the way at 0) as a tick removal
            newRating = -1;
        }
        if (ownTicks != null && ownTicks.size() > 0 && ownTicks.get(0).liked == rating) {
            // No need to update
            return;
        }
        // Update the user's tick status of this beer
        Intent i = new Intent(PosterService.ACTION_POSTTICK);
        i.putExtra(PosterService.EXTRA_BEERID, beerId);
        i.putExtra(PosterService.EXTRA_BEERNAME, beerName);
        i.putExtra(PosterService.EXTRA_USERID, getUser().getUserID());
        i.putExtra(PosterService.EXTRA_LIKED, newRating);
        i.putExtra(PosterService.EXTRA_MESSENGER, new Messenger(new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // Callback from the poster service; just refresh the tick status
                refreshOwnTick();
            }
        }));
        getActivity().startService(i);
    }

    protected void onDrinkingBeerClick() {
        // Update now drinking status
        Intent i = new Intent(PosterService.ACTION_SETDRINKINGSTATUS);
        i.putExtra(PosterService.EXTRA_NEWSTATUS, beerName);
        i.putExtra(PosterService.EXTRA_BEERID, beerId);
        getActivity().startService(i);
        // Manually set the last update date of the drinking status back, so a visit to the home screen refreshes it
        getSettings().saveUserSettings(new UserSettings(getUser().getUserID(), getUser().getUsername(),
                getUser().getPassword(), getUser().getDrinkingStatus(), getUser().isPremium(), new Date(1)));
    }

    protected void onShowFullScreenPhoto() {
        // Open the photo in a separate full screen image fragment
        load(FullScreenImageFragment_.builder().photoLowResUrl(ImageUrls.getBeerPhotoUrl(beerId))
                .photoHighResUrl(ImageUrls.getBeerPhotoHighResUrl(beerId)).build());
    }

    protected void onAddAvailability() {
        // Open availability adding screen
        load(AddAvailabilityFragment_.builder().beerId(beerId).beerName(beerName).build());
    }

    protected void onWantOrHaveBeerClick(AddToCellarFragment.CellarType cellarType) {
        // Open add to cellar screen
        load(AddToCellarFragment_.builder().beerId(beerId).beerName(beerName).cellarType(cellarType).build());
    }

    protected void onStyleClick() {
        // Infer style from the name; this is a bit of a hack but the RateBeer API doesn't give the style ID :(
        if (details == null) {
            return;
        }
        Style style = null;
        for (Style s : Style.ALL_STYLES.values()) {
            if (s.getName().equals(details.beerStyle)) {
                style = s;
                break;
            }
        }
        if (style == null) {
            Log.d(com.ratebeer.android.gui.components.helpers.Log.LOG_NAME,
                    "Wanted to get the beer style of " + beerName + " (#" + beerId + "), which is "
                            + details.beerStyle + " but can't find it in our own list.");
            publishException(null, getString(R.string.error_stylenotfound));
            return;
        }
        // Open style details screen
        load(StyleViewFragment_.builder().style(style).build());
    }

    protected void onBrewerClick() {
        load(BrewerViewFragment_.builder().brewerId(details.brewerId).build());
    }

    private void onReviewClick(Integer userId, String username) {
        // Start the user details screen
        load(UserViewFragment_.builder().userName(username).userId(userId).build());
    }

    protected void onStartPhotoUpload() {
        new ChoosePhotoFragment(this).show(getFragmentManager(), "");
    }

    public void onStartPhotoSnapping() {
        // Start an intent to snap a picture
        // http://stackoverflow.com/questions/1910608/android-action-image-capture-intent
        try {
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {

                File file = new File(
                        RateBeerForAndroid.DEFAULT_FILES_DIR + "/photos/" + Integer.toString(beerId) + ".jpg");
                if (!file.exists()) {
                    file.getParentFile().mkdirs();
                    file.createNewFile();
                }

                Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                i.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
                startActivityForResult(i, ACTIVITY_CAMERA);

            } else {
                publishException(null, getString(R.string.error_nocamera));
            }
        } catch (Exception e) {
            publishException(null, getString(R.string.error_nocamera));
        }
    }

    public void onStartPhotoPicking() {
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.setType("image/*");
        startActivityForResult(i, ACTIVITY_PICKPHOTO);
    }

    // Taken from http://stackoverflow.com/a/4470069/243165
    private String getImagePath(Uri uri) {
        String[] projection = { MediaStore.Images.Media.DATA };
        Cursor cursor = getActivity().managedQuery(uri, projection, null, null, null);
        if (cursor != null) {
            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            cursor.moveToFirst();
            return cursor.getString(column_index);
        } else
            return null;
    }

    // NOTE: The @OnActivityResult annotation doesn't work (yet) on Fragments
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        File photo = null;
        switch (requestCode) {
        case ACTIVITY_PICKPHOTO:

            if (data != null && data.getData() != null) {
                String selectedImagePath = getImagePath(data.getData());
                if (selectedImagePath != null) {
                    photo = new File(selectedImagePath);
                } else {
                    photo = new File(data.getData().getPath());
                }
            }
            // Note the fall through

        case ACTIVITY_CAMERA:

            if (photo == null) {
                photo = new File(
                        RateBeerForAndroid.DEFAULT_FILES_DIR + "/photos/" + Integer.toString(beerId) + ".jpg");
            }
            if (resultCode == Activity.RESULT_OK && photo != null && photo.exists()) {
                // Start an upload task for this photo
                Intent i = new Intent(PosterService.ACTION_UPLOADBEERPHOTO);
                i.putExtra(PosterService.EXTRA_BEERID, beerId);
                i.putExtra(PosterService.EXTRA_BEERNAME, beerName);
                i.putExtra(PosterService.EXTRA_PHOTO, photo);
                getActivity().startService(i);
            }
            break;
        }
    }

    private void onPlaceClick(Integer placeId, String placeName) {
        // Start the user details screen
        load(PlaceViewFragment_.builder().placeId(placeId).build());
    }

    @Override
    public void onTaskSuccessResult(CommandSuccessResult result) {
        if (result.getCommand().getMethod() == ApiMethod.GetBeerDetails) {
            publishDetails(((GetBeerDetailsCommand) result.getCommand()).getDetails());
        } else if (result.getCommand().getMethod() == ApiMethod.GetUserTicks) {
            publishOwnTick(((GetUserTicksCommand) result.getCommand()).getUserTicks());
        } else if (result.getCommand().getMethod() == ApiMethod.GetBeerRatings) {
            GetRatingsCommand grc = (GetRatingsCommand) result.getCommand();
            if (grc.getForUserId() == GetRatingsCommand.NO_USER) {
                publishRatings(grc.getRatings());
            } else {
                publishOwnRating(grc.getRatings());
            }
        } else if (result.getCommand().getMethod() == ApiMethod.GetBeerAvailability) {
            publishAvailability(((GetBeerAvailabilityCommand) result.getCommand()).getPlaces());
        } else if (result.getCommand().getMethod() == ApiMethod.GetBeerPhotoAvailable) {
            publishPhotoAvailability(((GetBeerPhotoAvailableCommand) result.getCommand()).hasPhotoAvailable());
        }
    }

    private void publishPhotoAvailability(Boolean photoIsAvailable) {
        this.photoIsAvailable = photoIsAvailable;
        if (photoIsAvailable == null) {
            return;
        }
        // Show the beer photo upload field, if appropriate
        setPhotoAvailability(photoIsAvailable);
    }

    private void publishDetails(BeerDetails details) {
        this.details = details;
        if (details == null) {
            return;
        }
        // Update the beer name and title
        this.beerName = details.beerName;
        // Show details
        setDetails(details);
    }

    private void publishOwnRating(ArrayList<BeerRating> ownRatings) {
        this.ownRatings = ownRatings;
        // Show the rating
        setOwnRating(this.ownRatings);
    }

    private void publishOwnTick(ArrayList<UserTick> allUserTicks) {
        if (allUserTicks == null) {
            this.ownTicks = null;
            setOwnTick(null);
            return;
        }

        // Unfortunately the API can only send all of the user's ticks at once so we have to find the current beer
        this.ownTicks = new ArrayList<GetUserTicksCommand.UserTick>();
        for (UserTick userTick : allUserTicks) {
            if (userTick.beerdId == beerId) {
                // Creat a list with only this beer's user tick in it
                this.ownTicks.add(userTick);
                break;
            }
        }
        // Show the tick
        setOwnTick(this.ownTicks);
    }

    private void publishRatings(ArrayList<BeerRating> ratings) {
        this.recentRatings = ratings;
        setRatings(ratings);
    }

    private void publishAvailability(ArrayList<PlaceSearchResult> places) {
        this.availability = places;
        setAvailability(availability);
    }

    public void setRatings(ArrayList<BeerRating> ratings) {
        // First remove our own rating, if it is in there (otherwise it would show twice)
        if (getUser() != null) {
            BeerRating toRemove = null;
            for (BeerRating rating : ratings) {
                if (rating.userId == getUser().getUserID()) {
                    toRemove = rating;
                    break;
                }
            }
            if (toRemove != null) {
                ratings.remove(toRemove);
            }
        }
        // Show the new list of ratings
        otherratingslabel.setVisibility(View.VISIBLE);
        recentRatingsAdapter.replace(ratings);
    }

    public void setAvailability(ArrayList<PlaceSearchResult> places) {
        // Show the new list of places that have this beer available
        if (availabilityView.getAdapter() == null) {
            availabilityView.setAdapter(new AvailabilityAdapter(getActivity(), places));
        } else {
            ((AvailabilityAdapter) availabilityView.getAdapter()).replace(places);
        }
    }

    /**
     * Overrides the different textual details about this beer, as shown as header
     * @param details The beer details object containing the texts to show
     */
    public void setDetails(BeerDetails details) {
        nameText.setText(details.beerName);
        brewernameButton.setText(getString(R.string.details_bybrewer, details.brewerName));
        brewernameButton.setVisibility(View.VISIBLE);
        boolean noScoreYet = details.overallPerc == GetBeerDetailsCommand.NO_SCORE_YET;
        noscoreyetText.setVisibility(noScoreYet ? View.VISIBLE : View.GONE);
        scoreCard.setVisibility(noScoreYet ? View.GONE : View.VISIBLE);
        scoreText.setText(String.format("%.0f", details.overallPerc));
        stylepctlText.setText(String.format("%.0f", details.stylePerc));
        ratingsText.setText(ratingsCount == UNKNOWN_RATINGS_COUNT ? "?" : Integer.toString(ratingsCount));
        abvstyleButton.setText(getString(R.string.details_abvstyle, details.beerStyle,
                String.format(DECIMAL_FORMATTER, details.alcohol)));
        abvstyleButton.setVisibility(View.VISIBLE);
        descriptionText.setText(Html.fromHtml(details.description == null || details.description.equals("")
                ? getString(R.string.details_nodescription)
                : details.description.replace("\n", "<br />")));
        descriptionText.setMovementMethod(new ScrollingMovementMethod());
        // Only show the buttons bar if we have a signed in user
        drinkingThisButton.setVisibility(getUser() != null ? View.VISIBLE : View.GONE);
        addAvailabilityButton.setVisibility(getUser() != null ? View.VISIBLE : View.GONE);
        // Only show the cellar buttons bar if we have a signed in premium user
        wantthisButton.setVisibility(getUser() != null && getUser().isPremium() ? View.VISIBLE : View.GONE);
        havethisButton.setVisibility(getUser() != null && getUser().isPremium() ? View.VISIBLE : View.GONE);
    }

    public void setPhotoAvailability(Boolean photoIsAvailable) {
        uploadphotoButton.setVisibility(photoIsAvailable != null && !photoIsAvailable ? View.VISIBLE : View.GONE);
    }

    public void setOwnRating(ArrayList<BeerRating> ownRatings) {
        if (ownRatings == null) {
            // Still loading and we didn't have a retained rating object
            return;
        }
        if (ownRatings.size() > 0) {
            BeerRating ownRating = ownRatings.get(0);
            ownratinglabel.setVisibility(View.VISIBLE);
            ownratingRow.setVisibility(View.VISIBLE);
            ownratingTotal.setText(Float.toString(PostRatingCommand.calculateTotal(ownRating.aroma,
                    ownRating.appearance, ownRating.flavor, ownRating.mouthfeel, ownRating.overall)));
            ownratingAroma.setText(Integer.toString(ownRating.aroma));
            ownratingAppearance.setText(Integer.toString(ownRating.appearance));
            ownratingTaste.setText(Integer.toString(ownRating.flavor));
            ownratingPalate.setText(Integer.toString(ownRating.mouthfeel));
            ownratingOverall.setText(Integer.toString(ownRating.overall));
            ownratingUsername.setText(ownRating.userName + " (" + Integer.toString(ownRating.rateCount) + ")");
            ownratingComments.setText(ownRating.comments
                    + (ownRating.timeUpdated != null ? " (" + displayDateFormat.format(ownRating.timeUpdated) + ")"
                            : ownRating.timeEntered != null
                                    ? " (" + displayDateFormat.format(ownRating.timeEntered) + ")"
                                    : ""));
            rateThisButton.setText(R.string.details_viewrerate);
        } else {
            ownratinglabel.setVisibility(View.GONE);
            ownratingRow.setVisibility(View.GONE);
            rateThisButton.setText(R.string.details_ratethis);
        }
        rateThisButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                onRateBeerClick();
            }
        });
    }

    public void setOwnTick(ArrayList<UserTick> ownTicks) {
        if (ownTicks == null) {
            // Still loading and we didn't have a retained ticks object
            return;
        }
        if (ownTicks.size() > 0) {
            UserTick ownTick = ownTicks.get(0);
            tickBar.setRating(ownTick.liked);
        }
        ticklabel.setVisibility(View.VISIBLE);
        tickBar.setVisibility(View.VISIBLE);
        tickBar.setOnRatingBarChangeListener(new RatingBar.OnRatingBarChangeListener() {
            @Override
            public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser) {
                if (fromUser)
                    onTickBarUpdated(rating);
            }
        });
    }

    @Override
    public void onTaskFailureResult(CommandFailureResult result) {
        publishException(null, result.getException());
    }

    /**
     * This fragment can beam NFC messages describing the currently-visible beer
     */
    @Override
    public Uri createNdefUri() {
        return Uri.parse("http://ratebeer.com/b/" + Integer.toString(beerId));
    }

    private class BeerRatingsAdapter extends ArrayAdapter<BeerRating> {

        private OnClickListener onRowClick = new OnClickListener() {
            @Override
            public void onClick(View v) {
                onReviewClick((Integer) v.findViewById(R.id.total).getTag(),
                        (String) v.findViewById(R.id.username).getTag());
            }
        };

        public BeerRatingsAdapter(Context context, List<BeerRating> objects) {
            super(context, objects);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            // Get the right view, using a ViewHolder
            BeerRatingViewHolder holder;
            if (convertView == null) {
                convertView = getActivity().getLayoutInflater().inflate(R.layout.list_item_rating, null);
                ActivityUtil.makeListItemClickable(convertView, onRowClick);
                holder = new BeerRatingViewHolder();
                holder.total = (TextView) convertView.findViewById(R.id.total);
                holder.aroma = (TextView) convertView.findViewById(R.id.aroma);
                holder.appearance = (TextView) convertView.findViewById(R.id.appearance);
                holder.taste = (TextView) convertView.findViewById(R.id.taste);
                holder.palate = (TextView) convertView.findViewById(R.id.palate);
                holder.overall = (TextView) convertView.findViewById(R.id.overall);
                holder.username = (TextView) convertView.findViewById(R.id.username);
                holder.comments = (TextView) convertView.findViewById(R.id.comments);
                convertView.setTag(holder);
            } else {
                holder = (BeerRatingViewHolder) convertView.getTag();
            }

            // Bind the data
            BeerRating item = getItem(position);
            holder.total.setTag(Integer.valueOf(item.userId));
            holder.total.setText(item.totalScore);
            holder.aroma.setText(Integer.toString(item.aroma));
            holder.appearance.setText(Integer.toString(item.appearance));
            holder.taste.setText(Integer.toString(item.flavor));
            holder.palate.setText(Integer.toString(item.mouthfeel));
            holder.overall.setText(Integer.toString(item.overall));
            holder.username.setTag(item.userName);
            holder.username.setText(item.userName + " (" + Integer.toString(item.rateCount) + "), " + item.country);
            holder.comments.setText(item.comments + (item.timeUpdated != null
                    ? " (" + displayDateFormat.format(item.timeUpdated) + ")"
                    : item.timeEntered != null ? " (" + displayDateFormat.format(item.timeEntered) + ")" : ""));

            return convertView;
        }

    }

    protected static class BeerRatingViewHolder {
        TextView total, aroma, appearance, taste, palate, overall, username, comments;
    }

    private class AvailabilityAdapter extends ArrayAdapter<PlaceSearchResult> {

        private OnClickListener onRowClick = new OnClickListener() {
            @Override
            public void onClick(View v) {
                onPlaceClick((Integer) v.findViewById(R.id.name).getTag(),
                        ((TextView) v.findViewById(R.id.name)).getText().toString());
            }
        };

        public AvailabilityAdapter(Context context, List<PlaceSearchResult> objects) {
            super(context, objects);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            // Get the right view, using a ViewHolder
            AvailabilityViewHolder holder;
            if (convertView == null) {
                convertView = getActivity().getLayoutInflater().inflate(R.layout.list_item_placesearchresult, null);
                ActivityUtil.makeListItemClickable(convertView, onRowClick);
                holder = new AvailabilityViewHolder();
                holder.name = (TextView) convertView.findViewById(R.id.name);
                holder.city = (TextView) convertView.findViewById(R.id.city);
                convertView.setTag(holder);
            } else {
                holder = (AvailabilityViewHolder) convertView.getTag();
            }

            // Bind the data
            PlaceSearchResult item = getItem(position);
            holder.name.setTag(item.placeId);
            holder.name.setText(item.placeName);
            holder.city.setText(item.city);

            return convertView;
        }

    }

    protected static class AvailabilityViewHolder {
        TextView name, city;
    }

    private class BeerRatingsViewAdapter extends MergeAdapter {

        public BeerRatingsViewAdapter() {

            // Add the rate button and the views for the user's own rating
            View fields = getActivity().getLayoutInflater().inflate(R.layout.fragment_beerratings, null);
            addView(fields, true);

            rateThisButton = (Button) fields.findViewById(R.id.ratethis);
            otherratingslabel = fields.findViewById(R.id.otherratingslabel);
            ownratinglabel = fields.findViewById(R.id.ownratinglabel);
            ownratingRow = fields.findViewById(R.id.ownrating);
            ownratingTotal = (TextView) fields.findViewById(R.id.total);
            ownratingAroma = (TextView) fields.findViewById(R.id.aroma);
            ownratingAppearance = (TextView) fields.findViewById(R.id.appearance);
            ownratingTaste = (TextView) fields.findViewById(R.id.taste);
            ownratingPalate = (TextView) fields.findViewById(R.id.palate);
            ownratingOverall = (TextView) fields.findViewById(R.id.overall);
            ownratingUsername = (TextView) fields.findViewById(R.id.username);
            ownratingComments = (TextView) fields.findViewById(R.id.comments);
            ticklabel = fields.findViewById(R.id.ticklabel);
            tickBar = (RatingBar) fields.findViewById(R.id.tick);

            // Add the recent ratings
            recentRatingsAdapter = new BeerRatingsAdapter(getActivity(), new ArrayList<BeerRating>());
            addAdapter(recentRatingsAdapter);

        }

    }

    private class BeerPagerAdapter extends PagerAdapter {

        private View pagerDetailsView;
        private ListView pagerRecentRatingsView;
        private View pagerAvailabilityView;

        public BeerPagerAdapter() {
            LayoutInflater inflater = getActivity().getLayoutInflater();
            pagerDetailsView = inflater.inflate(R.layout.fragment_beerdetails, null);
            pagerRecentRatingsView = (ListView) inflater.inflate(R.layout.fragment_pagerlist, null);
            pagerAvailabilityView = inflater.inflate(R.layout.fragment_beeravailability, null);

            // Ratings page
            pagerRecentRatingsView.setAdapter(new BeerRatingsViewAdapter());

            // Availability page
            availabilityEmpty = (TextView) pagerAvailabilityView.findViewById(R.id.empty);
            availabilityView = (ListView) pagerAvailabilityView.findViewById(R.id.list);
            availabilityView.setEmptyView(availabilityEmpty);
            addAvailabilityButton = (Button) pagerAvailabilityView.findViewById(R.id.addavailability);
            addAvailabilityButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    onAddAvailability();
                }
            });

            // Details page
            if (pagerDetailsView.findViewById(R.id.image) != null) {
                // Phone version; beer image, description and style button are on the details page in the pager
                imageView = (ImageView) pagerDetailsView.findViewById(R.id.image);
                abvstyleButton = (Button) pagerDetailsView.findViewById(R.id.abvstyle);
                descriptionText = (TextView) pagerDetailsView.findViewById(R.id.description);
                nameText = (TextView) pagerDetailsView.findViewById(R.id.name);
                brewernameButton = (Button) pagerDetailsView.findViewById(R.id.brewername);
            } else {
                // Tablet version; these fields are not in the pager but directly in the main layout
            }
            scoreCard = pagerDetailsView.findViewById(R.id.scorecard);
            noscoreyetText = (TextView) pagerDetailsView.findViewById(R.id.noscoreyet);
            scoreText = (TextView) pagerDetailsView.findViewById(R.id.overallpctl);
            stylepctlText = (TextView) pagerDetailsView.findViewById(R.id.stylepctl);
            ratingsText = (TextView) pagerDetailsView.findViewById(R.id.ratings);
            drinkingThisButton = (Button) pagerDetailsView.findViewById(R.id.drinkingthis);
            havethisButton = (Button) pagerDetailsView.findViewById(R.id.havethis);
            wantthisButton = (Button) pagerDetailsView.findViewById(R.id.wantthis);
            uploadphotoButton = (Button) pagerDetailsView.findViewById(R.id.uploadphoto);
            abvstyleButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    onStyleClick();
                }
            });
            brewernameButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    onBrewerClick();
                }
            });
            drinkingThisButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    onDrinkingBeerClick();
                }
            });
            havethisButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    onWantOrHaveBeerClick(CellarType.Have);
                }
            });
            wantthisButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    onWantOrHaveBeerClick(CellarType.Want);
                }
            });
            uploadphotoButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    onStartPhotoUpload();
                    // When circumventing the photo taking on debugging: 
                    //onActivityResult(ACTIVITY_CAMERA, Activity.RESULT_OK, null);
                }
            });

        }

        @Override
        public int getCount() {
            return 3;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            switch (position) {
            case 0:
                return getActivity().getString(R.string.app_details).toUpperCase(Locale.getDefault());
            case 1:
                return getActivity().getString(R.string.details_recentratings).toUpperCase(Locale.getDefault());
            case 2:
                return getActivity().getString(R.string.details_availability).toUpperCase(Locale.getDefault());
            }
            return null;
        }

        @Override
        public Object instantiateItem(View container, int position) {
            switch (position) {
            case 0:
                ((ViewPager) container).addView(pagerDetailsView, 0);
                return pagerDetailsView;
            case 1:
                ((ViewPager) container).addView(pagerRecentRatingsView, 0);
                return pagerRecentRatingsView;
            case 2:
                ((ViewPager) container).addView(pagerAvailabilityView, 0);
                return pagerAvailabilityView;
            }
            return null;
        }

        @Override
        public void destroyItem(View container, int position, Object object) {
            ((ViewPager) container).removeView((View) object);
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == (View) object;
        }

    }

}