at.ac.tuwien.detlef.fragments.EpisodeListFragment.java Source code

Java tutorial

Introduction

Here is the source code for at.ac.tuwien.detlef.fragments.EpisodeListFragment.java

Source

/* *************************************************************************
 *  Copyright 2012 The detlef developers                                   *
 *                                                                         *
 *  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, see <http://www.gnu.org/licenses/>.  *
 ************************************************************************* */

package at.ac.tuwien.detlef.fragments;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import at.ac.tuwien.detlef.R;
import at.ac.tuwien.detlef.Singletons;
import at.ac.tuwien.detlef.adapters.EpisodeListAdapter;
import at.ac.tuwien.detlef.db.EpisodeDAO;
import at.ac.tuwien.detlef.db.PlaylistDAO;
import at.ac.tuwien.detlef.db.PodcastDAO;
import at.ac.tuwien.detlef.domain.Episode;
import at.ac.tuwien.detlef.domain.Episode.ActionState;
import at.ac.tuwien.detlef.domain.EpisodePersistence;
import at.ac.tuwien.detlef.domain.EpisodeSortChoice;
import at.ac.tuwien.detlef.domain.Podcast;
import at.ac.tuwien.detlef.filter.EpisodeFilter;
import at.ac.tuwien.detlef.filter.FilterChain;
import at.ac.tuwien.detlef.filter.KeywordFilter;
import at.ac.tuwien.detlef.filter.NewFilter;
import at.ac.tuwien.detlef.filter.PodcastFilter;
import at.ac.tuwien.detlef.models.EpisodeListModel;
import at.ac.tuwien.detlef.settings.GpodderSettings;
import at.ac.tuwien.detlef.util.GUIUtils;

/**
 * The {@link Fragment} that displays a list of {@link Episode Episodes}.
 */
public class EpisodeListFragment extends ListFragment
        implements EpisodeDAO.OnEpisodeChangeListener, PlaylistDAO.OnPlaylistChangeListener {

    private static final String TAG = EpisodeListFragment.class.getName();
    private static final String BUNDLE_SELECTED_PODCAST = "BUNDLE_SELECTED_PODCAST";
    private static final String BUNDLE_FILTERS = "BUNDLE_FILTERS";

    private static final long ID_NONE = -1;

    private EpisodeListModel model;
    private EpisodeListAdapter adapter;
    private FilterChain filter = new FilterChain();
    private Podcast filteredByPodcast = null;
    private OnEpisodeSelectedListener listener;
    private PlaylistDAO playlistDAO;

    private GpodderSettings settings;

    /**
     * The parent activity must implement this interface in order to interact
     * with this fragment. The listener is called whenever an episode is
     * clicked.
     */
    public interface OnEpisodeSelectedListener {
        void onEpisodeSelected(Episode episode);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            listener = (OnEpisodeSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(String.format("%s must implement %s", activity.toString(),
                    OnEpisodeSelectedListener.class.getName()));
        }
    }

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

        Log.d(TAG, "onCreate(" + savedInstanceState + ")");

        EpisodeDAO dao = Singletons.i().getEpisodeDAO();
        dao.addEpisodeChangedListener(this);
        playlistDAO = Singletons.i().getPlaylistDAO();
        playlistDAO.addPlaylistChangedListener(this);

        List<Episode> eplist = dao.getAllEpisodes();
        model = new EpisodeListModel(eplist);

        adapter = new EpisodeListAdapter(getActivity(), android.R.layout.simple_list_item_1,
                new ArrayList<Episode>(eplist));
        setListAdapter(adapter);

        restoreSortOrder();
        restoreFilter(savedInstanceState);
    }

    /**
     * Restores the {@link EpisodeFilter episode filters}, e.g. after the screen
     * has been rotated.
     *
     * @param savedInstanceState
     */
    private void restoreFilter(Bundle savedInstanceState) {

        if (savedInstanceState == null) {
            return;
        }

        try {
            FilterChain pFilter = (FilterChain) savedInstanceState.getSerializable(BUNDLE_FILTERS);
            setFilter(pFilter);
            refresh();
        } catch (Exception e) {
            Log.e(TAG, "Exception restoring filter chain", e);
        }

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);

        return inflater.inflate(R.layout.episode_fragment_layout, container, false);
    }

    @Override
    public void onActivityCreated(Bundle savedState) {
        super.onActivityCreated(savedState);
        registerForContextMenu(getListView());

        /* Restore selected podcast. */

        if (savedState != null) {
            long id = savedState.getLong(BUNDLE_SELECTED_PODCAST, ID_NONE);
            if (id != ID_NONE) {
                PodcastDAO dao = Singletons.i().getPodcastDAO();
                filteredByPodcast = dao.getPodcastById(id);
                filterByPodcast();
            }
        }
        /* restore sort order */
        restoreSortOrder();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        /*
         * Save the currently selected podcast's ID in order to be able to
         * restore filter settings later on.
         */
        outState.putSerializable(BUNDLE_FILTERS, getFilter());
        long id = (filteredByPodcast == null ? ID_NONE : filteredByPodcast.getId());
        outState.putLong(BUNDLE_SELECTED_PODCAST, id);
    }

    @Override
    public void onDestroy() {
        EpisodeDAO dao = Singletons.i().getEpisodeDAO();
        dao.removeEpisodeChangedListener(this);
        playlistDAO.removePlaylistChangeListener(this);

        super.onDestroy();
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        Episode episode = (Episode) v.getTag();
        listener.onEpisodeSelected(episode);
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        MenuInflater inflater = getActivity().getMenuInflater();
        inflater.inflate(R.menu.episode_context, menu);

    }

    /**
     * Called whenever a podcast is clicked in the PodListFragment. Filters the
     * episode list to display only episodes belonging to the specified podcast.
     * If podcast is null, all episodes are shown.
     */
    public void setPodcast(Podcast podcast) {
        boolean selectionChanged = false;
        if (filteredByPodcast != podcast) {
            selectionChanged = true;
        }

        filteredByPodcast = podcast;
        filterByPodcast();

        /* Scroll to the top of the list. */
        if (selectionChanged) {
            setSelection(0);
        }

        /* restore sort order */
        restoreSortOrder();
    }

    private void restoreSortOrder() {
        settings = Singletons.i().getGpodderSettings();
        this.sortEpisodeList(settings.getSortChoice(), settings.isAscending());
    }

    private void filterByPodcast() {

        adapter.clear();
        if (filteredByPodcast == null) {
            getFilter().removeEpisodeFilter(new PodcastFilter());
        } else {
            getFilter().putEpisodeFilter(new PodcastFilter().setPodcast(filteredByPodcast));
        }
        refresh();
    }

    private void filterByPodcastOnUiThread() {
        Activity activity = getActivity();
        if (activity == null) {
            return;
        }

        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                filterByPodcast();
            }
        });
    }

    @Override
    public void onEpisodeChanged(Episode episode) {
        updateEpisodeList();
    }

    @Override
    public void onEpisodeAdded(final Episode episode) {
        Activity activity = getActivity();
        if (activity == null) {
            return;
        }

        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                model.addEpisode(episode);
            }
        });
        filterByPodcastOnUiThread();
    }

    @Override
    public void onEpisodeDeleted(final Episode episode) {
        Activity activity = getActivity();
        if (activity == null) {
            return;
        }

        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                model.removeEpisode(episode);
                adapter.notifyDataSetChanged();
            }
        });
    }

    /**
     * Updates the displayed list based on the current model contents. Ensures
     * that UI methods are called on the UI thread.
     */
    private void updateEpisodeList() {
        Activity activity = getActivity();
        if (activity == null) {
            return;
        }

        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                adapter.notifyDataSetChanged();
            }
        });
    }

    /**
     * Handles a click on the combined start download / abort download / delete
     * episode button. The action taken depends on the episode's storage state.
     */
    public void onDownloadTrashClick(View v) {
        Episode episode = ((Episode) v.getTag());
        switch (episode.getStorageState()) {
        case NOT_ON_DEVICE:
            try {
                GUIUtils.showToast(String.format("Downloading %s", episode.getTitle()), getActivity(), TAG);
                EpisodePersistence.download(episode);
            } catch (IOException e) {
                GUIUtils.showToast(getActivity().getString(R.string.cannot_download_episode), getActivity(), TAG);
            }
            break;
        case DOWNLOADING:
            GUIUtils.showToast("Download aborted", getActivity(), TAG);
            EpisodePersistence.cancelDownload(episode);
            break;
        case DOWNLOADED:
            GUIUtils.showToast(String.format("Deleted %s", episode.getTitle()), getActivity(), TAG);
            EpisodePersistence.delete(episode);
            Singletons.i().getEpisodeDAO().update(episode);
            break;
        default:
            Log.e(TAG, "Unknown storage state encountered");
        }
    }

    /**
     * Handles clicks on the mark read/unread button
     *
     * @param v The view of the button
     */
    public void onMarkReadUnreadClick(View v) {
        Episode episode = ((Episode) v.getTag());

        EpisodeDAO dao = Singletons.i().getEpisodeDAO();
        switch (episode.getActionState()) {
        case DOWNLOAD: // fall-through
        case NEW: // fall-through
        case PLAY:
            episode.setActionState(ActionState.DELETE);
            dao.update(episode);
            break;
        case DELETE:
            episode.setActionState(ActionState.NEW);
            dao.update(episode);
            break;
        default:
            Log.e(TAG, "Unknown action state encountered");
        }

        adapter.notifyDataSetChanged();
    }

    /**
     * Sorts the episode list by a specific choice.
     *
     * @param choice The sort choice
     * @param ascending Whether to sort ascending/descending
     */
    public void sortEpisodeList(EpisodeSortChoice choice, final boolean ascending) {
        Comparator<Episode> comparator = null;

        switch (choice) {
        case Podcast:
            comparator = new Comparator<Episode>() {
                @Override
                public int compare(Episode lhs, Episode rhs) {
                    int diff = (int) (rhs.getPodcast().getId() - lhs.getPodcast().getId());
                    return (ascending ? -1 * diff : diff);
                }
            };
            break;
        case ReleaseDate:
            comparator = new Comparator<Episode>() {
                @Override
                public int compare(Episode lhs, Episode rhs) {
                    int diff = (int) (rhs.getReleased() - lhs.getReleased());
                    return (ascending ? -1 * diff : diff);
                }
            };
            break;
        default:
            throw new IllegalArgumentException("Illegal sort choice");
        }

        adapter.sort(comparator);
        updateEpisodeList();
    }

    /**
     * Sets a keyword for the episode search.
     *
     * @param newText The keyword to search for.
     */
    public void setKeyword(String newText) {
        filter.putEpisodeFilter(new KeywordFilter().setKeyword(newText));
        refresh();
    }

    /**
     * Refreshes the episode list view.
     */
    public void refresh() {
        adapter.clear();

        List<Episode> filteredEpisodes = new ArrayList<Episode>();
        for (Episode e : model.getAll()) {
            if (filter.filter(e)) {
                continue;
            }
            filteredEpisodes.add(e);
        }
        adapter.addAll(filteredEpisodes);

        restoreSortOrder();
        updateEpisodeList();

    }

    /**
     * @return The FilterChain currently associated with this
     *         EpisodeListFragment. This method must not return null.
     */
    public FilterChain getFilter() {
        return filter;
    }

    /**
     * Sets a filter for filtering the episodes.
     *
     * @param pFilter The filter to set.
     * @return this
     */
    public EpisodeListFragment setFilter(FilterChain pFilter) {

        if (pFilter == null) {
            throw new IllegalArgumentException("pFilter must not be null");
        }

        filter = pFilter;
        return this;
    }

    public void setReadFilter(boolean checked) {
        if (checked) {
            getFilter().putEpisodeFilter(new NewFilter());
        } else {
            getFilter().removeEpisodeFilter(new NewFilter());
        }
        refresh();
    }

    @Override
    public void onPlaylistEpisodeAdded(int position, Episode episode) {
        adapter.notifyDataSetChanged();
    }

    @Override
    public void onPlaylistEpisodePositionChanged(int firstPosition, int secondPosition) {
        // not of interest here
    }

    @Override
    public void onPlaylistEpisodeRemoved(int position) {
        adapter.notifyDataSetChanged();
    }
}