/* * Copyright (C) 2016 Simon Vig Therkildsen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.simonvt.cathode.ui.show; import android.app.Activity; import android.database.Cursor; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import butterknife.BindView; import butterknife.ButterKnife; import java.util.Locale; import javax.inject.Inject; import net.simonvt.cathode.CathodeApp; import net.simonvt.cathode.R; import net.simonvt.cathode.api.enumeration.ItemType; import net.simonvt.cathode.api.enumeration.ShowStatus; import net.simonvt.cathode.api.util.TraktUtils; import net.simonvt.cathode.database.SimpleCursor; import net.simonvt.cathode.database.SimpleCursorLoader; import net.simonvt.cathode.database.SimpleMergeCursor; import net.simonvt.cathode.images.ImageType; import net.simonvt.cathode.images.ImageUri; import net.simonvt.cathode.jobqueue.Job; import net.simonvt.cathode.provider.CollectLoader; import net.simonvt.cathode.provider.DatabaseContract; import net.simonvt.cathode.provider.DatabaseContract.CommentColumns; import net.simonvt.cathode.provider.DatabaseContract.EpisodeColumns; import net.simonvt.cathode.provider.DatabaseContract.PersonColumns; import net.simonvt.cathode.provider.DatabaseContract.RelatedShowsColumns; import net.simonvt.cathode.provider.DatabaseContract.ShowCastColumns; import net.simonvt.cathode.provider.DatabaseContract.ShowColumns; import net.simonvt.cathode.provider.DatabaseContract.ShowGenreColumns; import net.simonvt.cathode.provider.DatabaseContract.UserColumns; import net.simonvt.cathode.provider.DatabaseSchematic.Tables; import net.simonvt.cathode.provider.ProviderSchematic; import net.simonvt.cathode.provider.ProviderSchematic.Comments; import net.simonvt.cathode.provider.ProviderSchematic.RelatedShows; import net.simonvt.cathode.provider.ProviderSchematic.Seasons; import net.simonvt.cathode.provider.ProviderSchematic.ShowGenres; import net.simonvt.cathode.provider.ProviderSchematic.Shows; import net.simonvt.cathode.provider.WatchedLoader; import net.simonvt.cathode.scheduler.EpisodeTaskScheduler; import net.simonvt.cathode.scheduler.PersonTaskScheduler; import net.simonvt.cathode.scheduler.ShowTaskScheduler; import net.simonvt.cathode.settings.TraktTimestamps; import net.simonvt.cathode.ui.LibraryType; import net.simonvt.cathode.ui.NavigationListener; import net.simonvt.cathode.ui.comments.LinearCommentsAdapter; import net.simonvt.cathode.ui.dialog.CheckInDialog; import net.simonvt.cathode.ui.dialog.CheckInDialog.Type; import net.simonvt.cathode.ui.dialog.RatingDialog; import net.simonvt.cathode.ui.fragment.RefreshableAppBarFragment; import net.simonvt.cathode.ui.listener.SeasonClickListener; import net.simonvt.cathode.ui.lists.ListsDialog; import net.simonvt.cathode.util.DataHelper; import net.simonvt.cathode.util.DateUtils; import net.simonvt.cathode.util.Ids; import net.simonvt.cathode.util.Intents; import net.simonvt.cathode.util.SqlColumn; import net.simonvt.cathode.widget.CircleTransformation; import net.simonvt.cathode.widget.CircularProgressIndicator; import net.simonvt.cathode.widget.HiddenPaneLayout; import net.simonvt.cathode.widget.OverflowView; import net.simonvt.cathode.widget.RecyclerViewManager; import net.simonvt.cathode.widget.RemoteImageView; import net.simonvt.schematic.Cursors; import timber.log.Timber; public class ShowFragment extends RefreshableAppBarFragment { private static final String TAG = "net.simonvt.cathode.ui.show.ShowFragment"; private static final String ARG_SHOWID = "net.simonvt.cathode.ui.show.ShowFragment.showId"; private static final String ARG_TITLE = "net.simonvt.cathode.ui.show.ShowFragment.title"; private static final String ARG_OVERVIEW = "net.simonvt.cathode.ui.show.ShowFragment.overview"; private static final String ARG_TYPE = "net.simonvt.cathode.ui.show.ShowFragment.type"; private static final String DIALOG_RATING = "net.simonvt.cathode.ui.show.ShowFragment.ratingDialog"; private static final String DIALOG_LISTS_ADD = "net.simonvt.cathode.ui.show.ShowFragment.listsAddDialog"; private static final String DIALOG_COMMENT_UPDATE = "net.simonvt.cathode.ui.show.ShowFragment.updateCommentDialog"; private static final int LOADER_SHOW = 1; private static final int LOADER_SHOW_WATCH = 2; private static final int LOADER_SHOW_COLLECT = 3; private static final int LOADER_SHOW_GENRES = 4; private static final int LOADER_SHOW_SEASONS = 5; private static final int LOADER_SHOW_CAST = 6; private static final int LOADER_SHOW_USER_COMMENTS = 7; private static final int LOADER_SHOW_COMMENTS = 8; private static final int LOADER_RELATED = 9; private static final String[] SHOW_PROJECTION = new String[] { ShowColumns.TRAKT_ID, ShowColumns.TITLE, ShowColumns.YEAR, ShowColumns.AIR_TIME, ShowColumns.AIR_DAY, ShowColumns.NETWORK, ShowColumns.CERTIFICATION, ShowColumns.STATUS, ShowColumns.USER_RATING, ShowColumns.RATING, ShowColumns.OVERVIEW, ShowColumns.IN_WATCHLIST, ShowColumns.IN_COLLECTION_COUNT, ShowColumns.WATCHED_COUNT, ShowColumns.LAST_SYNC, ShowColumns.LAST_COMMENT_SYNC, ShowColumns.LAST_CREDITS_SYNC, ShowColumns.LAST_RELATED_SYNC, ShowColumns.HOMEPAGE, ShowColumns.TRAILER, ShowColumns.IMDB_ID, ShowColumns.TVDB_ID, ShowColumns.TMDB_ID, ShowColumns.NEEDS_SYNC, ShowColumns.HIDDEN_CALENDAR, }; private static final String[] EPISODE_PROJECTION = new String[] { EpisodeColumns.ID, EpisodeColumns.TITLE, EpisodeColumns.FIRST_AIRED, EpisodeColumns.SEASON, EpisodeColumns.EPISODE, EpisodeColumns.WATCHING, EpisodeColumns.CHECKED_IN, }; private static final String[] GENRES_PROJECTION = new String[] { ShowGenreColumns.GENRE, }; @Inject PersonTaskScheduler personScheduler; private NavigationListener navigationListener; private long showId; @BindView(R.id.hiddenPaneLayout) HiddenPaneLayout hiddenPaneLayout; @BindView(R.id.seasons) RecyclerView seasons; @BindView(R.id.seasonsEmpty) View seasonsEmpty; private SeasonsAdapter seasonsAdapter; private Cursor seasonsCursor; @BindView(R.id.rating) CircularProgressIndicator rating; @BindView(R.id.airtime) TextView airTime; @BindView(R.id.status) TextView status; @BindView(R.id.overview) TextView overview; @BindView(R.id.genresTitle) View genresTitle; @BindView(R.id.genres) TextView genres; @BindView(R.id.isWatched) TextView watched; @BindView(R.id.inCollection) TextView collection; @BindView(R.id.inWatchlist) TextView watchlist; @BindView(R.id.trailer) View trailer; @BindView(R.id.castParent) View castParent; @BindView(R.id.castHeader) View castHeader; @BindView(R.id.cast) LinearLayout cast; @BindView(R.id.castContainer) LinearLayout castContainer; @BindView(R.id.commentsParent) View commentsParent; @BindView(R.id.commentsHeader) View commentsHeader; @BindView(R.id.commentsContainer) LinearLayout commentsContainer; @BindView(R.id.relatedParent) View relatedParent; @BindView(R.id.relatedHeader) View relatedHeader; @BindView(R.id.related) LinearLayout related; @BindView(R.id.relatedContainer) LinearLayout relatedContainer; @BindView(R.id.websiteTitle) View websiteTitle; @BindView(R.id.website) TextView website; @BindView(R.id.viewOnTrakt) View viewOnTrakt; @BindView(R.id.viewOnImdb) View viewOnImdb; @BindView(R.id.viewOnTvdb) View viewOnTvdb; @BindView(R.id.viewOnTmdb) View viewOnTmdb; private Cursor userComments; private Cursor comments; @BindView(R.id.episodes) LinearLayout episodes; @BindView(R.id.toWatch) View toWatch; private EpisodeHolder toWatchHolder; private long toWatchId = -1; private String toWatchTitle; @BindView(R.id.lastWatched) @Nullable View lastWatched; private EpisodeHolder lastWatchedHolder; private long lastWatchedId = -1; @BindView(R.id.toCollect) View toCollect; private EpisodeHolder toCollectHolder; private long toCollectId = -1; @BindView(R.id.lastCollected) @Nullable View lastCollected; private EpisodeHolder lastCollectedHolder; private long lastCollectedId = -1; static class EpisodeHolder { @BindView(R.id.episodeScreenshot) RemoteImageView episodeScreenshot; @BindView(R.id.episodeTitle) TextView episodeTitle; @BindView(R.id.episodeAirTime) TextView episodeAirTime; @BindView(R.id.episodeEpisode) TextView episodeEpisode; @BindView(R.id.episodeOverflow) OverflowView episodeOverflow; public EpisodeHolder(View v) { ButterKnife.bind(this, v); } } @Inject ShowTaskScheduler showScheduler; @Inject EpisodeTaskScheduler episodeScheduler; private String showTitle; private String showOverview; private boolean inWatchlist; private int currentRating; private boolean calendarHidden; private LibraryType type; RecyclerViewManager seasonsManager; public static String getTag(long showId) { return TAG + "/" + showId + "/" + Ids.newId(); } public static Bundle getArgs(long showId, String title, String overview, LibraryType type) { if (showId < 0) { throw new IllegalArgumentException("showId must be >= 0"); } Bundle args = new Bundle(); args.putLong(ARG_SHOWID, showId); args.putString(ARG_TITLE, title); args.putString(ARG_OVERVIEW, overview); args.putSerializable(ARG_TYPE, type); return args; } public long getShowId() { return showId; } @Override public void onAttach(Activity activity) { super.onAttach(activity); navigationListener = (NavigationListener) activity; } @Override public void onCreate(Bundle inState) { super.onCreate(inState); Timber.d("ShowFragment#onCreate"); CathodeApp.inject(getActivity(), this); Bundle args = getArguments(); showId = args.getLong(ARG_SHOWID); showTitle = args.getString(ARG_TITLE); showOverview = args.getString(ARG_OVERVIEW); type = (LibraryType) args.getSerializable(ARG_TYPE); setTitle(showTitle); seasonsAdapter = new SeasonsAdapter(getActivity(), new SeasonClickListener() { @Override public void onSeasonClick(long showId, long seasonId, String showTitle, int seasonNumber) { navigationListener.onDisplaySeason(showId, seasonId, showTitle, seasonNumber, type); } }, type); } @Override public boolean onBackPressed() { if (hiddenPaneLayout != null) { final int state = hiddenPaneLayout.getState(); if (state == HiddenPaneLayout.STATE_OPEN || state == HiddenPaneLayout.STATE_OPENING) { hiddenPaneLayout.close(); return true; } } return super.onBackPressed(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle inState) { HiddenPaneLayout hiddenPane = (HiddenPaneLayout) inflater.inflate(R.layout.fragment_show_hiddenpanelayout, container, false); View v = super.onCreateView(inflater, hiddenPane, inState); hiddenPane.addView(v); inflater.inflate(R.layout.fragment_show_seasons, hiddenPane, true); return hiddenPane; } @Override public View createView(LayoutInflater inflater, ViewGroup container, Bundle inState) { return inflater.inflate(R.layout.fragment_show, container, false); } @Override public void onViewCreated(View view, Bundle inState) { super.onViewCreated(view, inState); overview.setText(showOverview); seasonsManager = new RecyclerViewManager(seasons, new LinearLayoutManager(getActivity()), seasonsEmpty); seasonsManager.setAdapter(seasonsAdapter); seasons.setAdapter(seasonsAdapter); ((DefaultItemAnimator) seasons.getItemAnimator()).setSupportsChangeAnimations(false); rating.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { RatingDialog.newInstance(RatingDialog.Type.SHOW, showId, currentRating) .show(getFragmentManager(), DIALOG_RATING); } }); castHeader.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { navigationListener.onDisplayCredits(ItemType.SHOW, showId, showTitle); } }); commentsHeader.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { navigationListener.onDisplayComments(ItemType.SHOW, showId); } }); relatedHeader.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { navigationListener.onDisplayRelatedShows(showId, showTitle); } }); toWatchHolder = new EpisodeHolder(toWatch); toWatch.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (toWatchId != -1) navigationListener.onDisplayEpisode(toWatchId, showTitle); } }); toWatchHolder.episodeOverflow.setListener(new OverflowView.OverflowActionListener() { @Override public void onPopupShown() { } @Override public void onPopupDismissed() { } @Override public void onActionSelected(int action) { switch (action) { case R.id.action_checkin: if (toWatchId != -1) { CheckInDialog.showDialogIfNecessary(getActivity(), Type.SHOW, toWatchTitle, toWatchId); } break; case R.id.action_checkin_cancel: if (toWatchId != -1) { showScheduler.cancelCheckin(); } break; case R.id.action_watched: if (toWatchId != -1) { episodeScheduler.setWatched(toWatchId, true); } break; } } }); if (lastWatched != null) { lastWatchedHolder = new EpisodeHolder(lastWatched); lastWatched.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (lastWatchedId != -1) { navigationListener.onDisplayEpisode(lastWatchedId, showTitle); } } }); lastWatchedHolder.episodeOverflow.addItem(R.id.action_unwatched, R.string.action_unwatched); lastWatchedHolder.episodeOverflow.setListener(new OverflowView.OverflowActionListener() { @Override public void onPopupShown() { } @Override public void onPopupDismissed() { } @Override public void onActionSelected(int action) { switch (action) { case R.id.action_unwatched: if (lastWatchedId != -1) { episodeScheduler.setWatched(lastWatchedId, false); } break; } } }); } toCollectHolder = new EpisodeHolder(toCollect); toCollect.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (toCollectId != -1) navigationListener.onDisplayEpisode(toCollectId, showTitle); } }); toCollectHolder.episodeOverflow.addItem(R.id.action_collection_add, R.string.action_collection_add); toCollectHolder.episodeOverflow.setListener(new OverflowView.OverflowActionListener() { @Override public void onPopupShown() { } @Override public void onPopupDismissed() { } @Override public void onActionSelected(int action) { switch (action) { case R.id.action_collection_add: if (toCollectId != -1) { episodeScheduler.setIsInCollection(toCollectId, true); } break; } } }); if (lastCollected != null) { lastCollectedHolder = new EpisodeHolder(lastCollected); lastCollected.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (lastCollectedId != -1) { navigationListener.onDisplayEpisode(lastCollectedId, showTitle); } } }); lastCollectedHolder.episodeOverflow.addItem(R.id.action_collection_remove, R.string.action_collection_remove); lastCollectedHolder.episodeOverflow.setListener(new OverflowView.OverflowActionListener() { @Override public void onPopupShown() { } @Override public void onPopupDismissed() { } @Override public void onActionSelected(int action) { switch (action) { case R.id.action_collection_add: if (lastCollectedId != -1) { episodeScheduler.setIsInCollection(lastCollectedId, true); } break; } } }); } getLoaderManager().initLoader(LOADER_SHOW, null, showCallbacks); getLoaderManager().initLoader(LOADER_SHOW_GENRES, null, genreCallbacks); getLoaderManager().initLoader(LOADER_SHOW_CAST, null, castCallback); getLoaderManager().initLoader(LOADER_SHOW_WATCH, null, episodeWatchCallbacks); getLoaderManager().initLoader(LOADER_SHOW_COLLECT, null, episodeCollectCallbacks); getLoaderManager().initLoader(LOADER_SHOW_SEASONS, null, seasonsLoader); getLoaderManager().initLoader(LOADER_SHOW_USER_COMMENTS, null, userCommentsLoader); getLoaderManager().initLoader(LOADER_SHOW_COMMENTS, null, commentsLoader); getLoaderManager().initLoader(LOADER_RELATED, null, relatedLoader); } private Job.OnDoneListener onDoneListener = new Job.OnDoneListener() { @Override public void onDone(Job job) { setRefreshing(false); } }; @Override public void onRefresh() { showScheduler.sync(showId, onDoneListener); } @Override public void createMenu(Toolbar toolbar) { super.createMenu(toolbar); Menu menu = toolbar.getMenu(); toolbar.inflateMenu(R.menu.fragment_show); if (inWatchlist) { menu.add(0, R.id.action_watchlist_remove, 300, R.string.action_watchlist_remove); } else { menu.add(0, R.id.action_watchlist_add, 300, R.string.action_watchlist_add); } if (calendarHidden) { menu.add(0, R.id.action_calendar_unhide, 400, R.string.action_calendar_unhide); } else { menu.add(0, R.id.action_calendar_hide, 400, R.string.action_calendar_hide); } } @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.menu_seasons: hiddenPaneLayout.toggle(); return true; case R.id.action_watchlist_remove: showScheduler.setIsInWatchlist(showId, false); return true; case R.id.action_watchlist_add: showScheduler.setIsInWatchlist(showId, true); return true; case R.id.menu_lists_add: ListsDialog.newInstance(DatabaseContract.ItemType.SHOW, showId) .show(getFragmentManager(), DIALOG_LISTS_ADD); return true; case R.id.action_calendar_hide: showScheduler.hideFromCalendar(showId, true); return true; case R.id.action_calendar_unhide: showScheduler.hideFromCalendar(showId, false); return true; } return super.onMenuItemClick(item); } private void updateShowView(final Cursor cursor) { if (cursor == null || !cursor.moveToFirst()) return; final long traktId = Cursors.getLong(cursor, ShowColumns.TRAKT_ID); String title = Cursors.getString(cursor, ShowColumns.TITLE); if (!TextUtils.equals(title, showTitle)) { showTitle = title; setTitle(title); } final String airTime = Cursors.getString(cursor, ShowColumns.AIR_TIME); final String airDay = Cursors.getString(cursor, ShowColumns.AIR_DAY); final String network = Cursors.getString(cursor, ShowColumns.NETWORK); final String certification = Cursors.getString(cursor, ShowColumns.CERTIFICATION); final String showStatus = Cursors.getString(cursor, ShowColumns.STATUS); final String backdropUri = ImageUri.create(ImageUri.ITEM_SHOW, ImageType.BACKDROP, showId); setBackdrop(backdropUri, true); showOverview = Cursors.getString(cursor, ShowColumns.OVERVIEW); inWatchlist = Cursors.getBoolean(cursor, ShowColumns.IN_WATCHLIST); final int inCollectionCount = Cursors.getInt(cursor, ShowColumns.IN_COLLECTION_COUNT); final int watchedCount = Cursors.getInt(cursor, ShowColumns.WATCHED_COUNT); currentRating = Cursors.getInt(cursor, ShowColumns.USER_RATING); final float ratingAll = Cursors.getFloat(cursor, ShowColumns.RATING); rating.setValue(ratingAll); calendarHidden = Cursors.getBoolean(cursor, ShowColumns.HIDDEN_CALENDAR); watched.setVisibility(watchedCount > 0 ? View.VISIBLE : View.GONE); collection.setVisibility(inCollectionCount > 0 ? View.VISIBLE : View.GONE); watchlist.setVisibility(inWatchlist ? View.VISIBLE : View.GONE); String airTimeString = null; if (airDay != null && airTime != null) { airTimeString = airDay + " " + airTime; } if (network != null) { if (airTimeString != null) { airTimeString += ", " + network; } else { airTimeString = network; } } if (certification != null) { if (airTimeString != null) { airTimeString += ", " + certification; } else { airTimeString = certification; } } this.airTime.setText(airTimeString); String statusString = null; if (showStatus != null) { final ShowStatus status = ShowStatus.fromValue(showStatus); switch (status) { case ENDED: statusString = getString(R.string.show_status_ended); break; case RETURNING: statusString = getString(R.string.show_status_returning); break; case CANCELED: statusString = getString(R.string.show_status_canceled); break; case IN_PRODUCTION: statusString = getString(R.string.show_status_in_production); break; case PLANNED: statusString = getString(R.string.show_status_planned); break; } } this.status.setText(statusString); this.overview.setText(showOverview); final String trailer = Cursors.getString(cursor, ShowColumns.TRAILER); if (!TextUtils.isEmpty(trailer)) { this.trailer.setVisibility(View.VISIBLE); this.trailer.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intents.openUrl(getActivity(), trailer); } }); } else { this.trailer.setVisibility(View.GONE); } final boolean needsSync = Cursors.getBoolean(cursor, ShowColumns.NEEDS_SYNC); if (needsSync) { Timber.d("Needs sync: %d", showId); showScheduler.sync(showId); } final long lastSync = Cursors.getLong(cursor, ShowColumns.LAST_SYNC); final long lastCommentSync = Cursors.getLong(cursor, ShowColumns.LAST_COMMENT_SYNC); if (TraktTimestamps.shouldSyncComments(lastCommentSync)) { showScheduler.syncComments(showId); } final long lastActorsSync = Cursors.getLong(cursor, ShowColumns.LAST_CREDITS_SYNC); if (lastSync > lastActorsSync) { showScheduler.syncCredits(showId, null); } final long lastRelatedSync = Cursors.getLong(cursor, ShowColumns.LAST_RELATED_SYNC); if (lastSync > lastRelatedSync) { showScheduler.syncRelated(showId, null); } final String website = Cursors.getString(cursor, ShowColumns.HOMEPAGE); if (!TextUtils.isEmpty(website)) { this.websiteTitle.setVisibility(View.VISIBLE); this.website.setVisibility(View.VISIBLE); this.website.setText(website); this.website.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intents.openUrl(getContext(), website); } }); } else { this.websiteTitle.setVisibility(View.GONE); this.website.setVisibility(View.GONE); } final String imdbId = Cursors.getString(cursor, ShowColumns.IMDB_ID); final int tvdbId = Cursors.getInt(cursor, ShowColumns.TVDB_ID); final int tmdbId = Cursors.getInt(cursor, ShowColumns.TMDB_ID); viewOnTrakt.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intents.openUrl(getContext(), TraktUtils.getTraktShowUrl(traktId)); } }); final boolean hasImdbId = !TextUtils.isEmpty(imdbId); if (hasImdbId) { viewOnImdb.setVisibility(View.VISIBLE); viewOnImdb.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intents.openUrl(getContext(), TraktUtils.getImdbUrl(imdbId)); } }); } else { viewOnImdb.setVisibility(View.GONE); } if (tvdbId > 0) { viewOnTvdb.setVisibility(View.VISIBLE); viewOnTvdb.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intents.openUrl(getContext(), TraktUtils.getTvdbUrl(tvdbId)); } }); } else { viewOnTvdb.setVisibility(View.GONE); } if (tmdbId > 0) { viewOnTmdb.setVisibility(View.VISIBLE); viewOnTmdb.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intents.openUrl(getContext(), TraktUtils.getTmdbTvUrl(tmdbId)); } }); } else { viewOnTmdb.setVisibility(View.GONE); } invalidateMenu(); } private void updateGenreViews(final Cursor cursor) { if (cursor.getCount() > 0) { StringBuilder sb = new StringBuilder(); final int genreColumnIndex = cursor.getColumnIndex(ShowGenreColumns.GENRE); cursor.moveToPosition(-1); while (cursor.moveToNext()) { sb.append(cursor.getString(genreColumnIndex)); if (!cursor.isLast()) sb.append(", "); } genresTitle.setVisibility(View.VISIBLE); genres.setVisibility(View.VISIBLE); genres.setText(sb.toString()); } else { genresTitle.setVisibility(View.GONE); genres.setVisibility(View.GONE); } } private void updateCastViews(Cursor c) { castContainer.removeAllViews(); final int count = c.getCount(); final int visibility = count > 0 ? View.VISIBLE : View.GONE; castParent.setVisibility(visibility); int index = 0; c.moveToPosition(-1); while (c.moveToNext() && index < 3) { View v = LayoutInflater.from(getActivity()).inflate(R.layout.item_person, castContainer, false); final long personId = Cursors.getLong(c, ShowCastColumns.PERSON_ID); final String headshotUrl = ImageUri.create(ImageUri.ITEM_PERSON, ImageType.PROFILE, personId); RemoteImageView headshot = (RemoteImageView) v.findViewById(R.id.headshot); headshot.addTransformation(new CircleTransformation()); headshot.setImage(headshotUrl); TextView name = (TextView) v.findViewById(R.id.person_name); name.setText(Cursors.getString(c, PersonColumns.NAME)); TextView character = (TextView) v.findViewById(R.id.person_job); character.setText(Cursors.getString(c, ShowCastColumns.CHARACTER)); v.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { navigationListener.onDisplayPerson(personId); } }); castContainer.addView(v); index++; } } private void updateRelatedView(Cursor related) { relatedContainer.removeAllViews(); final int count = related.getCount(); final int visibility = count > 0 ? View.VISIBLE : View.GONE; relatedParent.setVisibility(visibility); int index = 0; related.moveToPosition(-1); while (related.moveToNext() && index < 3) { View v = LayoutInflater.from(getActivity()) .inflate(R.layout.item_related, this.relatedContainer, false); final long relatedShowId = Cursors.getLong(related, RelatedShowsColumns.RELATED_SHOW_ID); final String title = Cursors.getString(related, ShowColumns.TITLE); final String overview = Cursors.getString(related, ShowColumns.OVERVIEW); final float rating = Cursors.getFloat(related, ShowColumns.RATING); final int votes = Cursors.getInt(related, ShowColumns.VOTES); final String poster = ImageUri.create(ImageUri.ITEM_SHOW, ImageType.POSTER, relatedShowId); RemoteImageView posterView = (RemoteImageView) v.findViewById(R.id.related_poster); posterView.addTransformation(new CircleTransformation()); posterView.setImage(poster); TextView titleView = (TextView) v.findViewById(R.id.related_title); titleView.setText(title); final String formattedRating = String.format(Locale.getDefault(), "%.1f", rating); String ratingText; if (votes >= 1000) { final float convertedVotes = votes / 1000.0f; final String formattedVotes = String.format(Locale.getDefault(), "%.1f", convertedVotes); ratingText = getString(R.string.related_rating_thousands, formattedRating, formattedVotes); } else { ratingText = getString(R.string.related_rating, formattedRating, votes); } TextView ratingView = (TextView) v.findViewById(R.id.related_rating); ratingView.setText(ratingText); v.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { navigationListener.onDisplayShow(relatedShowId, title, overview, LibraryType.WATCHED); } }); relatedContainer.addView(v); index++; } } private void updateEpisodeWatchViews(Cursor cursor) { if (cursor.moveToFirst()) { toWatch.setVisibility(View.VISIBLE); toWatchId = Cursors.getLong(cursor, EpisodeColumns.ID); final long firstAired = DataHelper.getFirstAired(cursor); final int season = Cursors.getInt(cursor, EpisodeColumns.SEASON); final int episode = Cursors.getInt(cursor, EpisodeColumns.EPISODE); toWatchTitle = DataHelper.getEpisodeTitle(getContext(), cursor, season, episode); final String toWatchEpisodeText = getString(R.string.season_x_episode_y, season, episode); toWatchHolder.episodeTitle.setText(toWatchTitle); toWatchHolder.episodeEpisode.setText(toWatchEpisodeText); final String screenshotUri = ImageUri.create(ImageUri.ITEM_EPISODE, ImageType.STILL, toWatchId); toWatchHolder.episodeScreenshot.setImage(screenshotUri); String firstAiredString = DateUtils.millisToString(getActivity(), firstAired, false); final boolean watching = Cursors.getBoolean(cursor, EpisodeColumns.WATCHING); final boolean checkedIn = Cursors.getBoolean(cursor, EpisodeColumns.CHECKED_IN); toWatchHolder.episodeOverflow.removeItems(); if (checkedIn) { toWatchHolder.episodeOverflow.addItem(R.id.action_checkin_cancel, R.string.action_checkin_cancel); firstAiredString = getResources().getString(R.string.show_watching); } else if (!watching) { toWatchHolder.episodeOverflow.addItem(R.id.action_checkin, R.string.action_checkin); toWatchHolder.episodeOverflow.addItem(R.id.action_watched, R.string.action_watched); } toWatchHolder.episodeAirTime.setText(firstAiredString); } else { toWatch.setVisibility(View.GONE); toWatchId = -1; } if (lastWatched != null) { if (cursor.moveToNext()) { lastWatched.setVisibility(View.VISIBLE); lastWatchedId = Cursors.getLong(cursor, EpisodeColumns.ID); final int season = Cursors.getInt(cursor, EpisodeColumns.SEASON); final int episode = Cursors.getInt(cursor, EpisodeColumns.EPISODE); final String title = DataHelper.getEpisodeTitle(getContext(), cursor, season, episode); lastWatchedHolder.episodeTitle.setText(title); final long firstAired = DataHelper.getFirstAired(cursor); final String firstAiredString = DateUtils.millisToString(getActivity(), firstAired, false); lastWatchedHolder.episodeAirTime.setText(firstAiredString); final String lastWatchedEpisodeText = getString(R.string.season_x_episode_y, season, episode); lastWatchedHolder.episodeEpisode.setText(lastWatchedEpisodeText); final String screenshotUri = ImageUri.create(ImageUri.ITEM_EPISODE, ImageType.STILL, lastWatchedId); lastWatchedHolder.episodeScreenshot.setImage(screenshotUri); } else { lastWatched.setVisibility(toWatchId == -1 ? View.GONE : View.INVISIBLE); lastWatchedId = -1; } } if (toWatchId == -1 && lastWatchedId == -1 && toCollectId == -1 && lastCollectedId == -1) { episodes.setVisibility(View.GONE); } else { episodes.setVisibility(View.VISIBLE); } } private void updateEpisodeCollectViews(Cursor cursor) { if (cursor.moveToFirst()) { toCollect.setVisibility(View.VISIBLE); toCollectId = Cursors.getLong(cursor, EpisodeColumns.ID); final int season = Cursors.getInt(cursor, EpisodeColumns.SEASON); final int episode = Cursors.getInt(cursor, EpisodeColumns.EPISODE); final String title = DataHelper.getEpisodeTitle(getContext(), cursor, season, episode); toCollectHolder.episodeTitle.setText(title); final long firstAired = DataHelper.getFirstAired(cursor); final String firstAiredString = DateUtils.millisToString(getActivity(), firstAired, false); toCollectHolder.episodeAirTime.setText(firstAiredString); final String toCollectEpisodeText = getString(R.string.season_x_episode_y, season, episode); toCollectHolder.episodeEpisode.setText(toCollectEpisodeText); final String screenshotUri = ImageUri.create(ImageUri.ITEM_EPISODE, ImageType.STILL, toCollectId); toCollectHolder.episodeScreenshot.setImage(screenshotUri); } else { toCollect.setVisibility(View.GONE); toCollectId = -1; } if (lastCollected != null) { if (cursor.moveToNext()) { lastCollected.setVisibility(View.VISIBLE); lastCollectedId = Cursors.getLong(cursor, EpisodeColumns.ID); final int season = Cursors.getInt(cursor, EpisodeColumns.SEASON); final int episode = Cursors.getInt(cursor, EpisodeColumns.EPISODE); final String title = DataHelper.getEpisodeTitle(getContext(), cursor, season, episode); lastCollectedHolder.episodeTitle.setText(title); final long firstAired = DataHelper.getFirstAired(cursor); final String firstAiredString = DateUtils.millisToString(getActivity(), firstAired, false); lastCollectedHolder.episodeAirTime.setText(firstAiredString); final String lastCollectedEpisodeText = getString(R.string.season_x_episode_y, season, episode); lastCollectedHolder.episodeEpisode.setText(lastCollectedEpisodeText); final String screenshotUri = ImageUri.create(ImageUri.ITEM_EPISODE, ImageType.STILL, lastCollectedId); lastCollectedHolder.episodeScreenshot.setImage(screenshotUri); } else { lastCollectedId = -1; lastCollected.setVisibility(View.INVISIBLE); } } if (toWatchId == -1 && lastWatchedId == -1 && toCollectId == -1 && lastCollectedId == -1) { episodes.setVisibility(View.GONE); } else { episodes.setVisibility(View.VISIBLE); } } private void updateComments() { LinearCommentsAdapter.updateComments(getContext(), commentsContainer, userComments, comments); commentsParent.setVisibility(View.VISIBLE); } private LoaderManager.LoaderCallbacks<SimpleCursor> showCallbacks = new LoaderManager.LoaderCallbacks<SimpleCursor>() { @Override public Loader<SimpleCursor> onCreateLoader(int id, Bundle args) { return new SimpleCursorLoader(getActivity(), Shows.withId(showId), SHOW_PROJECTION, null, null, null); } @Override public void onLoadFinished(Loader<SimpleCursor> cursorLoader, SimpleCursor data) { updateShowView(data); } @Override public void onLoaderReset(Loader<SimpleCursor> cursorLoader) { } }; private LoaderManager.LoaderCallbacks<SimpleCursor> genreCallbacks = new LoaderManager.LoaderCallbacks<SimpleCursor>() { @Override public Loader<SimpleCursor> onCreateLoader(int id, Bundle args) { return new SimpleCursorLoader(getActivity(), ShowGenres.fromShow(showId), GENRES_PROJECTION, null, null, ShowGenres.DEFAULT_SORT); } @Override public void onLoadFinished(Loader<SimpleCursor> cursorLoader, SimpleCursor data) { updateGenreViews(data); } @Override public void onLoaderReset(Loader<SimpleCursor> cursorLoader) { } }; private static final String[] CAST_PROJECTION = new String[] { Tables.SHOW_CAST + "." + ShowCastColumns.ID, Tables.SHOW_CAST + "." + ShowCastColumns.CHARACTER, Tables.SHOW_CAST + "." + ShowCastColumns.PERSON_ID, Tables.PEOPLE + "." + PersonColumns.NAME, }; private LoaderManager.LoaderCallbacks<SimpleCursor> castCallback = new LoaderManager.LoaderCallbacks<SimpleCursor>() { @Override public Loader<SimpleCursor> onCreateLoader(int id, Bundle args) { return new SimpleCursorLoader(getActivity(), ProviderSchematic.ShowCast.fromShow(showId), CAST_PROJECTION, Tables.PEOPLE + "." + PersonColumns.NEEDS_SYNC + "=0", null, null); } @Override public void onLoadFinished(Loader<SimpleCursor> loader, SimpleCursor data) { updateCastViews(data); } @Override public void onLoaderReset(Loader<SimpleCursor> loader) { } }; private LoaderManager.LoaderCallbacks<SimpleMergeCursor> episodeWatchCallbacks = new LoaderManager.LoaderCallbacks<SimpleMergeCursor>() { @Override public Loader<SimpleMergeCursor> onCreateLoader(int i, Bundle bundle) { return new WatchedLoader(getActivity(), showId, EPISODE_PROJECTION); } @Override public void onLoadFinished(Loader<SimpleMergeCursor> cursorLoader, SimpleMergeCursor cursor) { updateEpisodeWatchViews(cursor); } @Override public void onLoaderReset(Loader<SimpleMergeCursor> cursorLoader) { } }; private LoaderManager.LoaderCallbacks<SimpleMergeCursor> episodeCollectCallbacks = new LoaderManager.LoaderCallbacks<SimpleMergeCursor>() { @Override public Loader<SimpleMergeCursor> onCreateLoader(int i, Bundle bundle) { return new CollectLoader(getActivity(), showId, EPISODE_PROJECTION); } @Override public void onLoadFinished(Loader<SimpleMergeCursor> cursorLoader, SimpleMergeCursor cursor) { updateEpisodeCollectViews(cursor); } @Override public void onLoaderReset(Loader<SimpleMergeCursor> cursorLoader) { } }; private LoaderManager.LoaderCallbacks<SimpleCursor> seasonsLoader = new LoaderManager.LoaderCallbacks<SimpleCursor>() { @Override public Loader<SimpleCursor> onCreateLoader(int id, Bundle args) { return new SimpleCursorLoader(getActivity(), Seasons.fromShow(showId), SeasonsAdapter.PROJECTION, null, null, Seasons.DEFAULT_SORT); } @Override public void onLoadFinished(Loader<SimpleCursor> cursorLoader, SimpleCursor data) { seasonsCursor = data; seasonsAdapter.changeCursor(data); } @Override public void onLoaderReset(Loader<SimpleCursor> cursorLoader) { seasonsCursor = null; seasonsAdapter.changeCursor(null); } }; private static final String[] COMMENTS_PROJECTION = new String[] { SqlColumn.table(Tables.COMMENTS).column(CommentColumns.ID), SqlColumn.table(Tables.COMMENTS).column(CommentColumns.COMMENT), SqlColumn.table(Tables.COMMENTS).column(CommentColumns.SPOILER), SqlColumn.table(Tables.COMMENTS).column(CommentColumns.REVIEW), SqlColumn.table(Tables.COMMENTS).column(CommentColumns.CREATED_AT), SqlColumn.table(Tables.COMMENTS).column(CommentColumns.LIKES), SqlColumn.table(Tables.COMMENTS).column(CommentColumns.USER_RATING), SqlColumn.table(Tables.USERS).column(UserColumns.USERNAME), SqlColumn.table(Tables.USERS).column(UserColumns.NAME), SqlColumn.table(Tables.USERS).column(UserColumns.AVATAR), }; private LoaderManager.LoaderCallbacks<SimpleCursor> userCommentsLoader = new LoaderManager.LoaderCallbacks<SimpleCursor>() { @Override public Loader<SimpleCursor> onCreateLoader(int id, Bundle args) { return new SimpleCursorLoader(getContext(), Comments.fromShow(showId), COMMENTS_PROJECTION, CommentColumns.IS_USER_COMMENT + "=1", null, null); } @Override public void onLoadFinished(Loader<SimpleCursor> loader, SimpleCursor data) { userComments = data; updateComments(); } @Override public void onLoaderReset(Loader<SimpleCursor> loader) { } }; private LoaderManager.LoaderCallbacks<SimpleCursor> commentsLoader = new LoaderManager.LoaderCallbacks<SimpleCursor>() { @Override public Loader<SimpleCursor> onCreateLoader(int id, Bundle args) { return new SimpleCursorLoader(getContext(), Comments.fromShow(showId), COMMENTS_PROJECTION, CommentColumns.IS_USER_COMMENT + "=0 AND " + CommentColumns.SPOILER + "=0", null, CommentColumns.LIKES + " DESC LIMIT 3"); } @Override public void onLoadFinished(Loader<SimpleCursor> loader, SimpleCursor data) { comments = data; updateComments(); } @Override public void onLoaderReset(Loader<SimpleCursor> loader) { } }; private static final String[] RELATED_PROJECTION = new String[] { SqlColumn.table(Tables.SHOW_RELATED).column(RelatedShowsColumns.RELATED_SHOW_ID), SqlColumn.table(Tables.SHOWS).column(ShowColumns.TITLE), SqlColumn.table(Tables.SHOWS).column(ShowColumns.OVERVIEW), SqlColumn.table(Tables.SHOWS).column(ShowColumns.RATING), SqlColumn.table(Tables.SHOWS).column(ShowColumns.VOTES), }; private LoaderManager.LoaderCallbacks<SimpleCursor> relatedLoader = new LoaderManager.LoaderCallbacks<SimpleCursor>() { @Override public Loader<SimpleCursor> onCreateLoader(int id, Bundle args) { return new SimpleCursorLoader(getContext(), RelatedShows.fromShow(showId), RELATED_PROJECTION, null, null, RelatedShowsColumns.RELATED_INDEX + " ASC LIMIT 3"); } @Override public void onLoadFinished(Loader<SimpleCursor> loader, SimpleCursor data) { updateRelatedView(data); } @Override public void onLoaderReset(Loader<SimpleCursor> loader) { } }; }