package com.battlelancer.seriesguide.ui;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.os.AsyncTaskCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
import com.battlelancer.seriesguide.R;
import com.battlelancer.seriesguide.SgApp;
import com.battlelancer.seriesguide.api.Action;
import com.battlelancer.seriesguide.backend.settings.HexagonSettings;
import com.battlelancer.seriesguide.enums.EpisodeFlags;
import com.battlelancer.seriesguide.extensions.ActionsHelper;
import com.battlelancer.seriesguide.extensions.EpisodeActionsContract;
import com.battlelancer.seriesguide.extensions.ExtensionManager;
import com.battlelancer.seriesguide.loaders.EpisodeActionsLoader;
import com.battlelancer.seriesguide.provider.SeriesGuideContract.Episodes;
import com.battlelancer.seriesguide.provider.SeriesGuideContract.ListItemTypes;
import com.battlelancer.seriesguide.provider.SeriesGuideContract.Seasons;
import com.battlelancer.seriesguide.provider.SeriesGuideContract.Shows;
import com.battlelancer.seriesguide.settings.AppSettings;
import com.battlelancer.seriesguide.settings.DisplaySettings;
import com.battlelancer.seriesguide.settings.TraktCredentials;
import com.battlelancer.seriesguide.thetvdbapi.TvdbImageTools;
import com.battlelancer.seriesguide.ui.dialogs.CheckInDialogFragment;
import com.battlelancer.seriesguide.ui.dialogs.ManageListsDialogFragment;
import com.battlelancer.seriesguide.ui.dialogs.RateDialogFragment;
import com.battlelancer.seriesguide.util.DBUtils;
import com.battlelancer.seriesguide.util.EpisodeTools;
import com.battlelancer.seriesguide.util.LanguageTools;
import com.battlelancer.seriesguide.util.ServiceUtils;
import com.battlelancer.seriesguide.util.ShareUtils;
import com.battlelancer.seriesguide.util.ShowTools;
import com.battlelancer.seriesguide.util.TextTools;
import com.battlelancer.seriesguide.util.TimeTools;
import com.battlelancer.seriesguide.util.TraktRatingsTask;
import com.battlelancer.seriesguide.util.TraktTools;
import com.battlelancer.seriesguide.util.Utils;
import com.battlelancer.seriesguide.widgets.FeedbackView;
import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import com.uwetrottmann.androidutils.CheatSheet;
import java.util.Date;
import java.util.List;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import timber.log.Timber;
/**
* Displays general information about a show and its next episode.
*/
public class OverviewFragment extends Fragment implements
LoaderManager.LoaderCallbacks<Cursor>, EpisodeActionsContract {
public static final String ARG_INT_SHOW_TVDBID = "show_tvdbid";
private static final String TAG = "Overview";
private static final String ARG_EPISODE_TVDB_ID = "episodeTvdbId";
@BindView(R.id.containerOverviewShow) View containerShow;
@Nullable @BindView(R.id.viewStubOverviewFeedback) ViewStub feedbackViewStub;
@Nullable @BindView(R.id.feedbackViewOverview) FeedbackView feedbackView;
@BindView(R.id.containerOverviewEpisode) View containerEpisode;
@BindView(R.id.containerEpisodeActions) LinearLayout containerActions;
@BindView(R.id.background) ImageView imageBackground;
@BindView(R.id.imageViewOverviewEpisode) ImageView imageEpisode;
@BindView(R.id.episodeTitle) TextView textEpisodeTitle;
@BindView(R.id.episodeTime) TextView textEpisodeTime;
@BindView(R.id.episodeInfo) TextView textEpisodeNumbers;
@BindView(R.id.episode_primary_container) View containerEpisodePrimary;
@BindView(R.id.episode_meta_container) View containerEpisodeMeta;
@BindView(R.id.dividerHorizontalOverviewEpisodeMeta) View dividerEpisodeMeta;
@BindView(R.id.progress_container) View containerProgress;
@BindView(R.id.containerRatings) View containerRatings;
@BindView(R.id.dividerEpisodeButtons) View dividerEpisodeButtons;
@BindView(R.id.buttonEpisodeCheckin) Button buttonCheckin;
@BindView(R.id.buttonEpisodeWatched) Button buttonWatch;
@BindView(R.id.buttonEpisodeCollected) Button buttonCollect;
@BindView(R.id.buttonEpisodeSkip) Button buttonSkip;
@BindView(R.id.TextViewEpisodeDescription) TextView textDescription;
@BindView(R.id.labelDvd) View labelDvdNumber;
@BindView(R.id.textViewEpisodeDVDnumber) TextView textDvdNumber;
@BindView(R.id.labelGuestStars) View labelGuestStars;
@BindView(R.id.TextViewEpisodeGuestStars) TextView textGuestStars;
@BindView(R.id.textViewRatingsValue) TextView textRating;
@BindView(R.id.textViewRatingsVotes) TextView textRatingVotes;
@BindView(R.id.textViewRatingsUser) TextView textUserRating;
@BindView(R.id.buttonShowInfoIMDB) View buttonImdb;
@BindView(R.id.buttonTVDB) View buttonTvdb;
@BindView(R.id.buttonTrakt) View buttonTrakt;
@BindView(R.id.buttonWebSearch) Button buttonWebSearch;
@BindView(R.id.buttonShouts) Button buttonComments;
private Handler handler = new Handler();
private TraktRatingsTask traktRatingsTask;
private Unbinder unbinder;
private boolean isEpisodeDataAvailable;
private Cursor currentEpisodeCursor;
private int currentEpisodeTvdbId;
private boolean isShowDataAvailable;
private Cursor showCursor;
private int showTvdbId;
private String showTitle;
private boolean hasSetEpisodeWatched;
public static OverviewFragment newInstance(int showTvdbId) {
OverviewFragment f = new OverviewFragment();
// Supply index input as an argument.
Bundle args = new Bundle();
args.putInt(ARG_INT_SHOW_TVDBID, showTvdbId);
f.setArguments(args);
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
showTvdbId = getArguments().getInt(ARG_INT_SHOW_TVDBID);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_overview, container, false);
unbinder = ButterKnife.bind(this, v);
v.findViewById(R.id.imageButtonFavorite).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
toggleShowFavorited(v);
Utils.trackAction(getActivity(), TAG, "Toggle favorited");
}
});
containerEpisode.setVisibility(View.GONE);
// episode buttons
buttonCheckin.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
checkIn();
}
});
CheatSheet.setup(buttonCheckin);
buttonWatch.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
setEpisodeWatched();
}
});
CheatSheet.setup(buttonWatch);
buttonCollect.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
toggleEpisodeCollected();
}
});
buttonSkip.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
setEpisodeSkipped();
}
});
CheatSheet.setup(buttonSkip);
// ratings
containerRatings.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
rateEpisode();
}
});
CheatSheet.setup(containerRatings, R.string.action_rate);
// comments button
Utils.setVectorCompoundDrawable(getActivity().getTheme(), buttonComments,
R.attr.drawableComments);
// hide web search button
buttonWebSearch.setVisibility(View.GONE);
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Are we in a multi-pane layout?
View seasonsFragment = getActivity().findViewById(R.id.fragment_seasons);
boolean multiPane = seasonsFragment != null
&& seasonsFragment.getVisibility() == View.VISIBLE;
// do not display show info header in multi pane layout
containerShow.setVisibility(multiPane ? View.GONE : View.VISIBLE);
getLoaderManager().initLoader(OverviewActivity.OVERVIEW_SHOW_LOADER_ID, null, this);
getLoaderManager().initLoader(OverviewActivity.OVERVIEW_EPISODE_LOADER_ID, null, this);
setHasOptionsMenu(true);
}
@Override
public void onResume() {
super.onResume();
BaseNavDrawerActivity.ServiceActiveEvent event = EventBus.getDefault()
.getStickyEvent(BaseNavDrawerActivity.ServiceActiveEvent.class);
setEpisodeButtonsEnabled(event == null);
EventBus.getDefault().register(this);
loadEpisodeActionsDelayed();
}
@Override
public void onPause() {
EventBus.getDefault().unregister(this);
super.onPause();
}
@Override
public void onDestroyView() {
super.onDestroyView();
// Always cancel the request here, this is safe to call even if the image has been loaded.
// This ensures that the anonymous callback we have does not prevent the fragment from
// being garbage collected. It also prevents our callback from getting invoked even after the
// fragment is destroyed.
Picasso.with(getContext()).cancelRequest(imageEpisode);
unbinder.unbind();
}
@Override
public void onDestroy() {
super.onDestroy();
if (traktRatingsTask != null) {
traktRatingsTask.cancel(true);
traktRatingsTask = null;
}
if (handler != null) {
handler.removeCallbacks(episodeActionsRunnable);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.overview_fragment_menu, menu);
// enable/disable menu items
MenuItem itemShare = menu.findItem(R.id.menu_overview_share);
itemShare.setEnabled(isEpisodeDataAvailable);
itemShare.setVisible(isEpisodeDataAvailable);
MenuItem itemCalendar = menu.findItem(R.id.menu_overview_calendar);
itemCalendar.setEnabled(isEpisodeDataAvailable);
itemCalendar.setVisible(isEpisodeDataAvailable);
MenuItem itemManageLists = menu.findItem(R.id.menu_overview_manage_lists);
if (itemManageLists != null) {
itemManageLists.setEnabled(isEpisodeDataAvailable);
itemManageLists.setVisible(isEpisodeDataAvailable);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.menu_overview_share) {
shareEpisode();
return true;
} else if (itemId == R.id.menu_overview_calendar) {
createCalendarEvent();
return true;
} else if (itemId == R.id.menu_overview_manage_lists) {
if (isEpisodeDataAvailable) {
ManageListsDialogFragment.showListsDialog(
currentEpisodeCursor.getInt(EpisodeQuery._ID),
ListItemTypes.EPISODE, getFragmentManager());
}
Utils.trackAction(getActivity(), TAG, "Manage lists");
return true;
}
return super.onOptionsItemSelected(item);
}
private void createCalendarEvent() {
if (!isShowDataAvailable || !isEpisodeDataAvailable) {
return;
}
final int seasonNumber = currentEpisodeCursor.getInt(EpisodeQuery.SEASON);
final int episodeNumber = currentEpisodeCursor.getInt(EpisodeQuery.NUMBER);
final String episodeTitle = currentEpisodeCursor.getString(EpisodeQuery.TITLE);
// add calendar event
ShareUtils.suggestCalendarEvent(
getActivity(),
showCursor.getString(ShowQuery.SHOW_TITLE),
TextTools.getNextEpisodeString(getActivity(), seasonNumber, episodeNumber,
episodeTitle),
currentEpisodeCursor.getLong(EpisodeQuery.FIRST_RELEASE_MS),
showCursor.getInt(ShowQuery.SHOW_RUNTIME)
);
Utils.trackAction(getActivity(), TAG, "Add to calendar");
}
private void checkIn() {
if (!isEpisodeDataAvailable) {
return;
}
int episodeTvdbId = currentEpisodeCursor.getInt(EpisodeQuery._ID);
// check in
CheckInDialogFragment f = CheckInDialogFragment.newInstance(getActivity(),
episodeTvdbId);
// don't commit fragment change after onPause
if (f != null && isResumed()) {
f.show(getFragmentManager(), "checkin-dialog");
Utils.trackAction(getActivity(), TAG, "Check-In");
}
}
private void setEpisodeSkipped() {
changeEpisodeFlag(EpisodeFlags.SKIPPED);
Utils.trackAction(getActivity(), TAG, "Flag Skipped");
}
private void setEpisodeWatched() {
hasSetEpisodeWatched = true;
changeEpisodeFlag(EpisodeFlags.WATCHED);
Utils.trackAction(getActivity(), TAG, "Flag Watched");
}
private void changeEpisodeFlag(int episodeFlag) {
if (!isEpisodeDataAvailable) {
return;
}
final int season = currentEpisodeCursor.getInt(EpisodeQuery.SEASON);
final int episode = currentEpisodeCursor.getInt(EpisodeQuery.NUMBER);
EpisodeTools.episodeWatched(SgApp.from(getActivity()), showTvdbId,
currentEpisodeCursor.getInt(EpisodeQuery._ID), season, episode, episodeFlag);
}
private void rateEpisode() {
if (currentEpisodeTvdbId == 0) {
return;
}
RateDialogFragment.displayRateDialog(getActivity(), getFragmentManager(),
currentEpisodeTvdbId);
Utils.trackAction(getActivity(), TAG, "Rate (trakt)");
}
private void shareEpisode() {
if (!isEpisodeDataAvailable) {
return;
}
int seasonNumber = currentEpisodeCursor.getInt(EpisodeQuery.SEASON);
int episodeNumber = currentEpisodeCursor.getInt(EpisodeQuery.NUMBER);
String episodeTitle = currentEpisodeCursor.getString(EpisodeQuery.TITLE);
ShareUtils.shareEpisode(getActivity(), currentEpisodeTvdbId, seasonNumber, episodeNumber,
showTitle, episodeTitle);
Utils.trackAction(getActivity(), TAG, "Share");
}
private void toggleEpisodeCollected() {
if (!isEpisodeDataAvailable) {
return;
}
final int season = currentEpisodeCursor.getInt(EpisodeQuery.SEASON);
final int episode = currentEpisodeCursor.getInt(EpisodeQuery.NUMBER);
final boolean isCollected = currentEpisodeCursor.getInt(EpisodeQuery.COLLECTED) == 1;
EpisodeTools.episodeCollected(SgApp.from(getActivity()), showTvdbId,
currentEpisodeCursor.getInt(EpisodeQuery._ID), season, episode, !isCollected);
Utils.trackAction(getActivity(), TAG, "Toggle Collected");
}
private void toggleShowFavorited(View v) {
if (v.getTag() == null) {
return;
}
// store new value
boolean isFavorite = (Boolean) v.getTag();
SgApp.from(getActivity()).getShowTools().storeIsFavorite(showTvdbId, !isFavorite);
}
public static class EpisodeLoader extends CursorLoader {
private int showTvdbId;
public EpisodeLoader(Context context, int showTvdbId) {
super(context);
this.showTvdbId = showTvdbId;
setProjection(EpisodeQuery.PROJECTION);
}
@Override
public Cursor loadInBackground() {
// get episode id, set query params
int episodeId = (int) DBUtils.updateLatestEpisode(getContext(), showTvdbId);
setUri(Episodes.buildEpisodeUri(episodeId));
return super.loadInBackground();
}
}
interface EpisodeQuery {
String[] PROJECTION = new String[] {
Episodes._ID,
Episodes.NUMBER,
Episodes.ABSOLUTE_NUMBER,
Episodes.DVDNUMBER,
Episodes.SEASON,
Seasons.REF_SEASON_ID,
Episodes.IMDBID,
Episodes.TITLE,
Episodes.OVERVIEW,
Episodes.FIRSTAIREDMS,
Episodes.GUESTSTARS,
Episodes.RATING_GLOBAL,
Episodes.RATING_VOTES,
Episodes.RATING_USER,
Episodes.WATCHED,
Episodes.COLLECTED,
Episodes.IMAGE
};
int _ID = 0;
int NUMBER = 1;
int ABSOLUTE_NUMBER = 2;
int DVD_NUMBER = 3;
int SEASON = 4;
int SEASON_ID = 5;
int IMDBID = 6;
int TITLE = 7;
int OVERVIEW = 8;
int FIRST_RELEASE_MS = 9;
int GUESTSTARS = 10;
int RATING_GLOBAL = 11;
int RATING_VOTES = 12;
int RATING_USER = 13;
int WATCHED = 14;
int COLLECTED = 15;
int IMAGE = 16;
}
interface ShowQuery {
String[] PROJECTION = new String[] {
Shows._ID,
Shows.TITLE,
Shows.STATUS,
Shows.RELEASE_TIME,
Shows.RELEASE_WEEKDAY,
Shows.RELEASE_TIMEZONE,
Shows.RELEASE_COUNTRY,
Shows.NETWORK,
Shows.POSTER,
Shows.IMDBID,
Shows.RUNTIME,
Shows.FAVORITE,
Shows.LANGUAGE
};
int SHOW_TITLE = 1;
int SHOW_STATUS = 2;
int SHOW_RELEASE_TIME = 3;
int SHOW_RELEASE_WEEKDAY = 4;
int SHOW_RELEASE_TIMEZONE = 5;
int SHOW_RELEASE_COUNTRY = 6;
int SHOW_NETWORK = 7;
int SHOW_POSTER = 8;
int SHOW_IMDBID = 9;
int SHOW_RUNTIME = 10;
int SHOW_FAVORITE = 11;
int SHOW_LANGUAGE = 12;
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch (id) {
case OverviewActivity.OVERVIEW_EPISODE_LOADER_ID:
default:
return new EpisodeLoader(getActivity(), showTvdbId);
case OverviewActivity.OVERVIEW_SHOW_LOADER_ID:
return new CursorLoader(getActivity(), Shows.buildShowUri(String
.valueOf(showTvdbId)), ShowQuery.PROJECTION, null, null, null);
}
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if (!isAdded()) {
return;
}
switch (loader.getId()) {
case OverviewActivity.OVERVIEW_EPISODE_LOADER_ID:
isEpisodeDataAvailable = data != null && data.moveToFirst();
currentEpisodeCursor = data;
populateEpisodeViews(data);
break;
case OverviewActivity.OVERVIEW_SHOW_LOADER_ID:
isShowDataAvailable = data != null && data.moveToFirst();
showCursor = data;
if (isShowDataAvailable) {
populateShowViews(data);
}
break;
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
switch (loader.getId()) {
case OverviewActivity.OVERVIEW_EPISODE_LOADER_ID:
isEpisodeDataAvailable = false;
currentEpisodeCursor = null;
break;
case OverviewActivity.OVERVIEW_SHOW_LOADER_ID:
isShowDataAvailable = false;
showCursor = null;
break;
}
}
@Override
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(ExtensionManager.EpisodeActionReceivedEvent event) {
if (currentEpisodeTvdbId == event.episodeTvdbId) {
loadEpisodeActionsDelayed();
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventEpisodeTask(BaseNavDrawerActivity.ServiceActiveEvent event) {
setEpisodeButtonsEnabled(false);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventEpisodeTask(BaseNavDrawerActivity.ServiceCompletedEvent event) {
setEpisodeButtonsEnabled(true);
}
private void setEpisodeButtonsEnabled(boolean enabled) {
if (getView() == null) {
return;
}
buttonWatch.setEnabled(enabled);
buttonCollect.setEnabled(enabled);
buttonSkip.setEnabled(enabled);
buttonCheckin.setEnabled(enabled);
}
private void populateEpisodeViews(Cursor episode) {
maybeAddFeedbackView();
if (isEpisodeDataAvailable) {
// some episode properties
currentEpisodeTvdbId = episode.getInt(EpisodeQuery._ID);
// title
int season = episode.getInt(EpisodeQuery.SEASON);
int number = episode.getInt(EpisodeQuery.NUMBER);
if (DisplaySettings.preventSpoilers(getContext())) {
textEpisodeTitle.setText(TextTools.getEpisodeNumber(getContext(), season, number));
} else {
textEpisodeTitle.setText(episode.getString(EpisodeQuery.TITLE));
}
// number
StringBuilder infoText = new StringBuilder();
infoText.append(getString(R.string.season_number, String.valueOf(season)));
infoText.append(" ");
infoText.append(getString(R.string.episode_number, String.valueOf(number)));
int episodeAbsoluteNumber = episode.getInt(EpisodeQuery.ABSOLUTE_NUMBER);
if (episodeAbsoluteNumber > 0 && episodeAbsoluteNumber != number) {
infoText.append(" (").append(episodeAbsoluteNumber).append(")");
}
textEpisodeNumbers.setText(infoText);
// air date
long releaseTime = episode.getLong(EpisodeQuery.FIRST_RELEASE_MS);
if (releaseTime != -1) {
Date actualRelease = TimeTools.applyUserOffset(getContext(), releaseTime);
// "Oct 31 (Fri)" or "in 14 mins (Fri)"
String dateTime;
if (DisplaySettings.isDisplayExactDate(getContext())) {
dateTime = TimeTools.formatToLocalDateShort(getContext(), actualRelease);
} else {
dateTime = TimeTools.formatToLocalRelativeTime(getContext(), actualRelease);
}
textEpisodeTime.setText(getString(R.string.release_date_and_day, dateTime,
TimeTools.formatToLocalDay(actualRelease)));
} else {
textEpisodeTime.setText(null);
}
// make title and image clickable
containerEpisodePrimary.setOnClickListener(new OnClickListener() {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onClick(View view) {
// display episode details
Intent intent = new Intent(getActivity(), EpisodesActivity.class);
intent.putExtra(EpisodesActivity.InitBundle.EPISODE_TVDBID,
currentEpisodeTvdbId);
Utils.startActivityWithAnimation(getActivity(), intent, view);
}
});
containerEpisodePrimary.setFocusable(true);
// collected button
boolean isCollected = episode.getInt(EpisodeQuery.COLLECTED) == 1;
Utils.setCompoundDrawablesRelativeWithIntrinsicBounds(buttonCollect, 0,
isCollected ? R.drawable.ic_collected
: Utils.resolveAttributeToResourceId(getActivity().getTheme(),
R.attr.drawableCollect), 0, 0);
buttonCollect.setText(isCollected ? R.string.action_collection_remove
: R.string.action_collection_add);
CheatSheet.setup(buttonCollect, isCollected ? R.string.action_collection_remove
: R.string.action_collection_add);
// hide check-in if not connected to trakt or hexagon is enabled
boolean isConnectedToTrakt = TraktCredentials.get(getActivity()).hasCredentials();
boolean displayCheckIn = isConnectedToTrakt
&& !HexagonSettings.isEnabled(getActivity());
buttonCheckin.setVisibility(displayCheckIn ? View.VISIBLE : View.GONE);
dividerEpisodeButtons.setVisibility(displayCheckIn ? View.VISIBLE : View.GONE);
// load all other info
populateEpisodeDetails(episode);
// episode image
loadEpisodeImage(episode.getString(EpisodeQuery.IMAGE));
// episode actions
loadEpisodeActionsDelayed();
containerEpisodeMeta.setVisibility(View.VISIBLE);
} else {
// no next episode: display single line info text, remove other
// views
textEpisodeTitle.setText(R.string.no_nextepisode);
textEpisodeTime.setText(null);
textEpisodeNumbers.setText(null);
containerEpisodePrimary.setOnClickListener(null);
containerEpisodePrimary.setClickable(false);
containerEpisodePrimary.setFocusable(false);
containerEpisodeMeta.setVisibility(View.GONE);
loadEpisodeImage(null);
}
// enable/disable applicable menu items
getActivity().invalidateOptionsMenu();
// animate view into visibility
if (containerEpisode.getVisibility() == View.GONE) {
containerProgress.startAnimation(AnimationUtils
.loadAnimation(containerProgress.getContext(), android.R.anim.fade_out));
containerProgress.setVisibility(View.GONE);
containerEpisode.startAnimation(AnimationUtils
.loadAnimation(containerEpisode.getContext(), android.R.anim.fade_in));
containerEpisode.setVisibility(View.VISIBLE);
}
}
private void maybeAddFeedbackView() {
if (feedbackView != null || feedbackViewStub == null
|| !hasSetEpisodeWatched || !AppSettings.shouldAskForFeedback(getContext())) {
return; // can or should not add feedback view
}
feedbackView = (FeedbackView) feedbackViewStub.inflate();
feedbackViewStub = null;
if (feedbackView != null) {
feedbackView.setCallback(new FeedbackView.Callback() {
@Override
public void onRate() {
if (Utils.launchWebsite(getContext(), getString(R.string.url_store_page))) {
removeFeedbackView();
}
}
@Override
public void onFeedback() {
if (Utils.tryStartActivity(getContext(),
HelpActivity.getFeedbackEmailIntent(getContext()), true)) {
removeFeedbackView();
}
}
@Override
public void onDismiss() {
removeFeedbackView();
}
});
}
}
private void removeFeedbackView() {
if (feedbackView == null) {
return;
}
feedbackView.setVisibility(View.GONE);
AppSettings.setAskedForFeedback(getContext());
}
private void populateEpisodeDetails(Cursor episode) {
// description
populateEpisodeDescription();
// dvd number
boolean isShowingMeta = Utils.setLabelValueOrHide(labelDvdNumber, textDvdNumber,
episode.getDouble(EpisodeQuery.DVD_NUMBER));
// guest stars
isShowingMeta |= Utils.setLabelValueOrHide(labelGuestStars, textGuestStars,
TextTools.splitAndKitTVDBStrings(episode.getString(EpisodeQuery.GUESTSTARS)));
// hide divider if no meta is visible
dividerEpisodeMeta.setVisibility(isShowingMeta ? View.VISIBLE : View.GONE);
// trakt rating
textRating.setText(
TraktTools.buildRatingString(episode.getDouble(EpisodeQuery.RATING_GLOBAL)));
textRatingVotes.setText(TraktTools.buildRatingVotesString(getActivity(),
episode.getInt(EpisodeQuery.RATING_VOTES)));
// user rating
textUserRating.setText(TraktTools.buildUserRatingString(getActivity(),
episode.getInt(EpisodeQuery.RATING_USER)));
// IMDb button
String imdbId = episode.getString(EpisodeQuery.IMDBID);
if (TextUtils.isEmpty(imdbId) && showCursor != null) {
// fall back to show IMDb id
imdbId = showCursor.getString(ShowQuery.SHOW_IMDBID);
}
ServiceUtils.setUpImdbButton(imdbId, buttonImdb, TAG);
// TVDb button
final int episodeTvdbId = episode.getInt(EpisodeQuery._ID);
final int seasonTvdbId = episode.getInt(EpisodeQuery.SEASON_ID);
ServiceUtils.setUpTvdbButton(showTvdbId, seasonTvdbId, episodeTvdbId, buttonTvdb, TAG);
// trakt button
ServiceUtils.setUpTraktEpisodeButton(buttonTrakt, currentEpisodeTvdbId, TAG);
// trakt shouts button
final String episodeTitle = episode.getString(EpisodeQuery.TITLE);
buttonComments.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (currentEpisodeCursor != null && currentEpisodeCursor.moveToFirst()) {
Intent i = new Intent(getActivity(), TraktCommentsActivity.class);
i.putExtras(TraktCommentsActivity.createInitBundleEpisode(episodeTitle,
currentEpisodeTvdbId
));
Utils.startActivityWithAnimation(getActivity(), i, v);
Utils.trackAction(v.getContext(), TAG, "Comments");
}
}
});
// trakt ratings
loadTraktRatings();
}
/**
* Updates the episode description. Needs both show and episode data loaded.
*/
private void populateEpisodeDescription() {
if (!isShowDataAvailable || !isEpisodeDataAvailable) {
// no show or episode data available
return;
}
String overview = currentEpisodeCursor.getString(EpisodeQuery.OVERVIEW);
if (TextUtils.isEmpty(overview)) {
// no description available, show no translation available message
textDescription.setText(getString(R.string.no_translation,
LanguageTools.getShowLanguageStringFor(getContext(),
showCursor.getString(ShowQuery.SHOW_LANGUAGE)),
getString(R.string.tvdb)));
} else {
if (DisplaySettings.preventSpoilers(getContext())) {
textDescription.setText(R.string.no_spoilers);
} else {
textDescription.setText(overview);
}
}
}
@Override
public void loadEpisodeActions() {
if (currentEpisodeTvdbId == 0) {
// do not load actions if there is no episode
return;
}
Bundle args = new Bundle();
args.putInt(ARG_EPISODE_TVDB_ID, currentEpisodeTvdbId);
getLoaderManager().restartLoader(OverviewActivity.OVERVIEW_ACTIONS_LOADER_ID, args,
episodeActionsLoaderCallbacks);
}
Runnable episodeActionsRunnable = new Runnable() {
@Override
public void run() {
loadEpisodeActions();
}
};
@Override
public void loadEpisodeActionsDelayed() {
handler.removeCallbacks(episodeActionsRunnable);
handler.postDelayed(episodeActionsRunnable,
EpisodeActionsContract.ACTION_LOADER_DELAY_MILLIS);
}
private void loadEpisodeImage(String imagePath) {
if (TextUtils.isEmpty(imagePath)) {
imageEpisode.setImageDrawable(null);
return;
}
if (DisplaySettings.preventSpoilers(getContext())) {
// show image placeholder
imageEpisode.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
imageEpisode.setImageResource(R.drawable.ic_image_missing);
} else {
// try loading image
ServiceUtils.loadWithPicasso(getActivity(), TvdbImageTools.fullSizeUrl(imagePath))
.error(R.drawable.ic_image_missing)
.into(imageEpisode,
new Callback() {
@Override
public void onSuccess() {
imageEpisode.setScaleType(ImageView.ScaleType.CENTER_CROP);
}
@Override
public void onError() {
imageEpisode.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
}
}
);
}
}
private void loadTraktRatings() {
if (!isEpisodeDataAvailable
|| (traktRatingsTask != null
&& traktRatingsTask.getStatus() != AsyncTask.Status.FINISHED)) {
return;
}
int episodeTvdbId = currentEpisodeCursor.getInt(EpisodeQuery._ID);
int seasonNumber = currentEpisodeCursor.getInt(EpisodeQuery.SEASON);
int episodeNumber = currentEpisodeCursor.getInt(EpisodeQuery.NUMBER);
traktRatingsTask = new TraktRatingsTask(SgApp.from(getActivity()), showTvdbId,
episodeTvdbId, seasonNumber, episodeNumber);
AsyncTaskCompat.executeParallel(traktRatingsTask);
}
private void populateShowViews(@NonNull Cursor show) {
// set show title in action bar
showTitle = show.getString(ShowQuery.SHOW_TITLE);
ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(showTitle);
getActivity().setTitle(getString(R.string.description_overview) + showTitle);
}
if (getView() == null) {
return;
}
// status
final TextView statusText = (TextView) getView().findViewById(R.id.showStatus);
ShowTools.setStatusAndColor(statusText, show.getInt(ShowQuery.SHOW_STATUS));
// favorite
final ImageButton favorited = ButterKnife.findById(getView(), R.id.imageButtonFavorite);
boolean isFavorited = show.getInt(ShowQuery.SHOW_FAVORITE) == 1;
if (isFavorited) {
favorited.setImageResource(Utils.resolveAttributeToResourceId(getActivity().getTheme(),
R.attr.drawableStar));
} else {
favorited.setImageResource(Utils.resolveAttributeToResourceId(getActivity().getTheme(),
R.attr.drawableStar0));
}
favorited.setContentDescription(getString(isFavorited ? R.string.context_unfavorite
: R.string.context_favorite));
CheatSheet.setup(favorited, isFavorited ? R.string.context_unfavorite
: R.string.context_favorite);
favorited.setTag(isFavorited);
// poster background
TvdbImageTools.loadShowPosterAlpha(getActivity(), imageBackground,
show.getString(ShowQuery.SHOW_POSTER));
// next release day and time
StringBuilder timeAndNetwork = new StringBuilder();
int releaseTime = show.getInt(ShowQuery.SHOW_RELEASE_TIME);
String network = show.getString(ShowQuery.SHOW_NETWORK);
if (releaseTime != -1) {
int weekDay = show.getInt(ShowQuery.SHOW_RELEASE_WEEKDAY);
Date release = TimeTools.getShowReleaseDateTime(getActivity(),
TimeTools.getShowReleaseTime(releaseTime),
weekDay,
show.getString(ShowQuery.SHOW_RELEASE_TIMEZONE),
show.getString(ShowQuery.SHOW_RELEASE_COUNTRY),
network);
String dayString = TimeTools.formatToLocalDayOrDaily(getActivity(), release, weekDay);
String timeString = TimeTools.formatToLocalTime(getActivity(), release);
// "Mon 08:30"
timeAndNetwork.append(dayString).append(" ").append(timeString);
}
// network
if (!TextUtils.isEmpty(network)) {
if (timeAndNetwork.length() != 0) {
timeAndNetwork.append(" ");
}
timeAndNetwork.append(getString(R.string.show_on_network, network));
}
((TextView) getView().findViewById(R.id.showmeta)).setText(timeAndNetwork.toString());
// episode description might need show language, so update it here as well
populateEpisodeDescription();
}
private LoaderManager.LoaderCallbacks<List<Action>> episodeActionsLoaderCallbacks =
new LoaderManager.LoaderCallbacks<List<Action>>() {
@Override
public Loader<List<Action>> onCreateLoader(int id, Bundle args) {
int episodeTvdbId = args.getInt(ARG_EPISODE_TVDB_ID);
return new EpisodeActionsLoader(getActivity(), episodeTvdbId);
}
@Override
public void onLoadFinished(Loader<List<Action>> loader, List<Action> data) {
if (!isAdded()) {
return;
}
if (data == null) {
Timber.e("onLoadFinished: did not receive valid actions");
} else {
Timber.d("onLoadFinished: received %s actions", data.size());
}
ActionsHelper.populateActions(getActivity().getLayoutInflater(),
getActivity().getTheme(), containerActions, data, TAG);
}
@Override
public void onLoaderReset(Loader<List<Action>> loader) {
ActionsHelper.populateActions(getActivity().getLayoutInflater(),
getActivity().getTheme(), containerActions, null, TAG);
}
};
}