/* DroidBeard - a free, open-source Android app for managing SickBeard Copyright (C) 2014-2015 Robert Carr 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 3 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 com.rastating.droidbeard.fragments; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.support.v4.widget.SwipeRefreshLayout; import android.view.ActionMode; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.rastating.droidbeard.comparators.EpisodeComparator; import com.rastating.droidbeard.R; import com.rastating.droidbeard.entities.Episode; import com.rastating.droidbeard.entities.Season; import com.rastating.droidbeard.entities.TVShow; import com.rastating.droidbeard.entities.TVShowSummary; import com.rastating.droidbeard.net.ApiResponseListener; import com.rastating.droidbeard.net.DeleteShowTask; import com.rastating.droidbeard.net.EpisodeSearchTask; import com.rastating.droidbeard.net.FetchShowTask; import com.rastating.droidbeard.net.SetEpisodeStatusTask; import com.rastating.droidbeard.net.SetPausedTask; import com.rastating.droidbeard.net.SickbeardAsyncTask; import com.rastating.droidbeard.net.SickbeardTaskPool; import com.rastating.droidbeard.net.TaskPoolSubscriber; import com.rastating.droidbeard.ui.CrossFader; import com.rastating.droidbeard.ui.EpisodeItem; import com.rastating.droidbeard.ui.EpisodeItemClickListener; import com.rastating.droidbeard.ui.LoadingAnimation; import com.rastating.droidbeard.ui.SeasonTable; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class ShowFragment extends DroidbeardFragment implements ApiResponseListener<TVShow>, EpisodeItemClickListener, View.OnClickListener { private TVShowSummary mShowSummary; private ImageView mBanner; private ImageView mLoadingImage; private View mDataContainer; private TVShow mShow; private TextView mAirs; private TextView mStatus; private TextView mLocation; private TextView mQuality; private TextView mLanguage; private ImageView mLanguageIcon; private ImageView mFlattenFolders; private ImageView mPaused; private ImageView mAirByDate; private LinearLayout mSeasonContainer; private Button mPauseButton; private Button mDeleteButton; private SwipeRefreshLayout swipeRefreshLayout; private ArrayList<EpisodeItem> mSelectedEpisodes; private boolean mDisposingActionMode; private ActionMode mActionMode; private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { // Called when the action mode is created; startActionMode() was called @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { // Inflate a menu resource providing context menu items MenuInflater inflater = mode.getMenuInflater(); inflater.inflate(R.menu.episode_context_menu, menu); return true; } // Called each time the action mode is shown. Always called after onCreateActionMode, but // may be called multiple times if the mode is invalidated. @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; // Return false if nothing is done } // Called when the user selects a contextual menu item @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case 0: mode.finish(); // Action picked, so close the CAB return true; case R.id.set_archived: case R.id.set_ignored: case R.id.set_skipped: case R.id.set_wanted: onSetStatusItemSelected(item); mode.finish(); return true; case R.id.search: onEpisodeSearchItemSelected(); mode.finish(); return true; default: return false; } } // Called when the user exits the action mode @Override public void onDestroyActionMode(ActionMode mode) { mActionMode = null; mDisposingActionMode = true; for (int i = mSelectedEpisodes.size() - 1; i >= 0 && mSelectedEpisodes.size() > 0; i--) { EpisodeItem item = mSelectedEpisodes.get(i); item.setChecked(false); } mSelectedEpisodes.clear(); mDisposingActionMode = false; getActivity().setTheme(R.style.SickBeardTheme); } }; private boolean mReturnToUpcomingEpisodes; public ShowFragment() { mShowSummary = null; mSelectedEpisodes = new ArrayList<EpisodeItem>(); } public void setTvShowSummary(TVShowSummary show) { mShowSummary = show; setTitle(show.getName()); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final Context contextThemeWrapper = new ContextThemeWrapper(getActivity(), R.style.SickBeardTheme_LightActionBar); LayoutInflater localInflater = inflater.cloneInContext(contextThemeWrapper); View root = localInflater.inflate(R.layout.fragment_show, container, false); swipeRefreshLayout = (SwipeRefreshLayout) root.findViewById(R.id.swipe_refresh_layout); swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { onRefreshButtonPressed(); swipeRefreshLayout.setRefreshing(true); } }); swipeRefreshLayout.setColorSchemeResources(R.color.materialPrimaryDark, R.color.materialPrimary, R.color.navigation_list_item_selected, R.color.unaired_episode_background); if (savedInstanceState != null) { mShowSummary = (TVShowSummary) savedInstanceState.getParcelable("summary"); } // Get references to all required views. mBanner = (ImageView) root.findViewById(R.id.banner); mLoadingImage = (ImageView) root.findViewById(R.id.loading); mDataContainer = root.findViewById(R.id.data); mAirs = (TextView) root.findViewById(R.id.airs); mStatus = (TextView) root.findViewById(R.id.status); mLocation = (TextView) root.findViewById(R.id.location); mQuality = (TextView) root.findViewById(R.id.quality); mLanguage = (TextView) root.findViewById(R.id.language); mLanguageIcon = (ImageView) root.findViewById(R.id.language_icon); mFlattenFolders = (ImageView) root.findViewById(R.id.flatten_folders); mPaused = (ImageView) root.findViewById(R.id.paused); mAirByDate = (ImageView) root.findViewById(R.id.air_by_date); mSeasonContainer = (LinearLayout) root.findViewById(R.id.season_container); mPauseButton = (Button) root.findViewById(R.id.toggle_pause); mDeleteButton = (Button) root.findViewById(R.id.delete); mPauseButton.setOnClickListener(this); mDeleteButton.setOnClickListener(this); mDataContainer.setAlpha(0.0f); mLoadingImage.setAlpha(1.0f); mLoadingImage.startAnimation(new LoadingAnimation()); onRefreshButtonPressed(); return root; } @Override public void onApiRequestFinished(SickbeardAsyncTask sender, TVShow result) { if (activityStillExists()) { mShow = result; if (mShow != null) { populateViews(); } new CrossFader(mLoadingImage, mDataContainer, 500).start(); } } @Override public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } @Override public void onRefreshButtonPressed() { // Start loading animation. //mDataContainer.setAlpha(0.0f); //mLoadingImage.setAlpha(1.0f); //mLoadingImage.startAnimation(new LoadingAnimation()); // Start fetching the show information. FetchShowTask task = new FetchShowTask(getActivity()); task.addResponseListener(this); task.start(mShowSummary.getTvDbId()); if(swipeRefreshLayout != null) { swipeRefreshLayout.post(new Runnable() { @Override public void run() { swipeRefreshLayout.setRefreshing(false); } }); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelable("summary", mShowSummary); } private void populateViews() { if (mShow.getBanner() != null) { mBanner.setImageBitmap(mShow.getBanner()); } mAirs.setText(mShow.getAirs() + " on " + mShow.getNetwork()); mStatus.setText(mShow.getStatus()); mLocation.setText(mShow.getLocation()); mQuality.setText(mShow.getQuality()); mLanguage.setText(mShow.getLanguage().getCode()); mLanguageIcon.setImageResource(mShow.getLanguage().getIconResId()); mFlattenFolders.setImageResource(mShow.getFlattenFolders() ? R.drawable.yes16 : R.drawable.no16); mAirByDate.setImageResource(mShow.getAirByDate() ? R.drawable.yes16 : R.drawable.no16); setupPauseViews(mShow.getPaused()); mSeasonContainer.removeAllViews(); List<Season> seasons = mShow.getSeasons(); if (seasons != null) { for (Season season : seasons) { SeasonTable table = new SeasonTable(getActivity(), season); table.setTitle(season.getTitle()); List<Episode> episodes = season.getEpisodes(); Collections.sort(episodes, new EpisodeComparator()); Collections.reverse(episodes); for (Episode episode : episodes) { EpisodeItem item = table.addEpisode(episode); item.setOnItemClickListener(this); item.setOnCreateContextMenuListener(this); } mSeasonContainer.addView(table); } } } private void setupPauseViews(boolean paused) { mPaused.setImageResource(paused ? R.drawable.yes16 : R.drawable.no16); mPauseButton.setText(paused ? getString(R.string.unpause) : getString(R.string.pause)); } @Override public void onItemClick(EpisodeItem item, int seasonNumber, int episodeNumber, String name) { if (!mDisposingActionMode) { if (item.isChecked()) { mSelectedEpisodes.add(item); } else { mSelectedEpisodes.remove(item); } if (mActionMode == null && mSelectedEpisodes.size() > 0) { getActivity().setTheme(R.style.SickBeardTheme_LightActionBar); mActionMode = getActivity().startActionMode(mActionModeCallback); } else if (mActionMode != null && mSelectedEpisodes.size() == 0) { mActionMode.finish(); } if (mActionMode != null && mSelectedEpisodes.size() > 0) { mActionMode.setTitle(String.valueOf(mSelectedEpisodes.size()) + " selected"); } } } private SetEpisodeStatusTask buildEpisodeStatusTask(SeasonTable seasonTable, EpisodeItem episodeItem, long tvdbid, int season, int episode, String status) { // Create finals for use in callback... final EpisodeItem finalEpisodeItem = episodeItem; final SeasonTable finalSeasonTable = seasonTable; final int finalEpisode = episode; final String finalStatus = status; SetEpisodeStatusTask task = new SetEpisodeStatusTask(getActivity(), tvdbid, season, episode); task.addResponseListener(new ApiResponseListener<Boolean>() { @Override public void onApiRequestFinished(SickbeardAsyncTask sender, Boolean result) { if (result) { if (finalEpisode > 0) { finalEpisodeItem.setStatus(finalStatus); finalEpisodeItem.setChecked(false); } else { finalSeasonTable.setChecked(false); for (EpisodeItem item : finalSeasonTable.getEpisodeItems()) { item.setStatus(finalStatus); item.setChecked(false); } } } } }); return task; } private void onSetStatusItemSelected(MenuItem item) { // Group selected episodes into a list of individual episodes and full seasons... ArrayList<SeasonTable> selectedSeasons = new ArrayList<SeasonTable>(); ArrayList<EpisodeItem> selectedEpisodes = new ArrayList<EpisodeItem>(); for (EpisodeItem episode : mSelectedEpisodes) { SeasonTable seasonTable = episode.getSeasonTable(); if (seasonTable.allEpisodesChecked()) { if (!selectedSeasons.contains(seasonTable)) { selectedSeasons.add(seasonTable); } } else { selectedEpisodes.add(episode); } } String status = ""; if (item.getItemId() == R.id.set_wanted) { status = "wanted"; } else if (item.getItemId() == R.id.set_skipped) { status = "skipped"; } else if (item.getItemId() == R.id.set_ignored) { status = "ignored"; } else { status = "archived"; } SickbeardTaskPool<String> pool = new SickbeardTaskPool<String>(); for (SeasonTable seasonTable : selectedSeasons) { Season season = seasonTable.getSeason(); SetEpisodeStatusTask task = buildEpisodeStatusTask(seasonTable, null, season.getTVDBID(), season.getSeasonNumber(), -1, status); pool.addTask(task); } for (EpisodeItem episodeItem : selectedEpisodes) { Episode episode = episodeItem.getEpisode(); SetEpisodeStatusTask task = buildEpisodeStatusTask(null, episodeItem, episode.getTVDBID(), episode.getSeasonNumber(), episode.getEpisodeNumber(), status); pool.addTask(task); } final ProgressDialog dialog = createProgressDialog("Performing Status Updates", "Please wait..."); dialog.show(); pool.setTaskPoolSubscriber(new TaskPoolSubscriber() { @Override public void executionFinished() { dialog.dismiss(); mSelectedEpisodes.clear(); } }); pool.start(status); } private void onEpisodeSearchItemSelected() { SickbeardTaskPool<Void> pool = new SickbeardTaskPool<Void>(); for (EpisodeItem episodeItem : mSelectedEpisodes) { Episode episode = episodeItem.getEpisode(); EpisodeSearchTask task = new EpisodeSearchTask(getActivity(), episode.getTVDBID(), episode.getSeasonNumber(), episode.getEpisodeNumber()); pool.addTask(task); } final ProgressDialog dialog = createProgressDialog("Searching for Selected Episodes", "Please wait..."); dialog.show(); pool.setTaskPoolSubscriber(new TaskPoolSubscriber() { @Override public void executionFinished() { dialog.dismiss(); for (int i = mSelectedEpisodes.size() - 1; i >= 0; i--) { EpisodeItem item = mSelectedEpisodes.get(i); item.setChecked(false); } mSelectedEpisodes.clear(); onRefreshButtonPressed(); } }); pool.start(); } private ProgressDialog createProgressDialog(String title, String message) { ProgressDialog dialog = new ProgressDialog(getActivity()); dialog.setTitle(title); dialog.setMessage(message); dialog.setCancelable(false); dialog.setIndeterminate(true); return dialog; } public void setShouldReturnToUpcomingEpisodes(boolean value) { mReturnToUpcomingEpisodes = value; } public boolean shouldReturnToUpcomingEpisodes() { return mReturnToUpcomingEpisodes; } @Override public void onClick(View v) { if (v == mPauseButton) { mShow.setPaused(!mShow.getPaused()); setupPauseViews(mShow.getPaused()); new SetPausedTask(ShowFragment.this.getActivity(), mShowSummary.getTvDbId()).start(mShow.getPaused()); } else if (v == mDeleteButton) { new AlertDialog.Builder(ShowFragment.this.getActivity()) .setTitle("Confirm") .setMessage("Are you sure you want to delete this show?") .setPositiveButton("Yes", new DialogInterface.OnClickListener() { public void onClick(DialogInterface d, int whichButton) { final ProgressDialog dialog = createProgressDialog("Deleting Show", "Please wait..."); dialog.show(); DeleteShowTask task = new DeleteShowTask(ShowFragment.this.getActivity()); task.addResponseListener(new ApiResponseListener<Boolean>() { @Override public void onApiRequestFinished(SickbeardAsyncTask sender, Boolean result) { dialog.dismiss(); getMainActivity().displayAndRefreshShowsFragment(); } }); task.start(mShowSummary.getTvDbId()); } }) .setNegativeButton("No", null) .show(); } } }