Redesign tv shows details, to not use tabs or expandable lists in episodes list

First version, to be improved visually
This commit is contained in:
Synced Synapse 2016-12-15 20:10:55 +00:00
parent d993126c7e
commit 43371b9480
18 changed files with 894 additions and 1033 deletions

View File

@ -200,6 +200,23 @@ public abstract class AbstractCursorListFragment extends AbstractListFragment
*/
abstract protected String getListSyncType();
/**
* Should return the {@link LibrarySyncService} syncID if this fragment
* synchronizes a single item. The itemId that should be synced must returned by {@link #getSyncItemID()}
* @return {@link LibrarySyncService} SyncID if syncing a single item. Null if not aplicable
*/
protected String getSyncID() {
return null;
}
/**
* Should return the item ID for SyncID returned by {@link #getSyncID()}
* @return -1 if not used.
*/
protected int getSyncItemID() {
return -1;
}
/**
* Event bus post. Called when the syncing process ended
*
@ -238,10 +255,8 @@ public abstract class AbstractCursorListFragment extends AbstractListFragment
@Override
public void onServiceConnected(LibrarySyncService librarySyncService) {
if(SyncUtils.isLibrarySyncing(
librarySyncService,
HostManager.getInstance(getActivity()).getHostInfo(),
getListSyncType())) {
HostInfo hostInfo = HostManager.getInstance(getActivity()).getHostInfo();
if(SyncUtils.isLibrarySyncing(librarySyncService, hostInfo, getListSyncType())) {
showRefreshAnimation();
}
}
@ -263,6 +278,13 @@ public abstract class AbstractCursorListFragment extends AbstractListFragment
// Start the syncing process
Intent syncIntent = new Intent(this.getActivity(), LibrarySyncService.class);
syncIntent.putExtra(getListSyncType(), true);
String syncID = getSyncID();
int itemId = getSyncItemID();
if ((syncID != null) && (itemId != -1)) {
syncIntent.putExtra(syncID, itemId);
}
getActivity().startService(syncIntent);
}

View File

@ -208,11 +208,11 @@ public class MoviesActivity extends BaseActivity
selectedMovieTitle = vh.movieTitle;
selectedMovieId = vh.movieId;
final MovieDetailsFragment movieDetailsFragment = MovieDetailsFragment.newInstance(vh);
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
// Set up transitions
if (Utils.isLollipopOrLater()) {
final MovieDetailsFragment movieDetailsFragment = MovieDetailsFragment.newInstance(vh);
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
android.support.v4.app.SharedElementCallback seCallback = new android.support.v4.app.SharedElementCallback() {
@Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
@ -230,31 +230,24 @@ public class MoviesActivity extends BaseActivity
}
};
movieDetailsFragment.setEnterSharedElementCallback(seCallback);
movieDetailsFragment.setEnterTransition(TransitionInflater
.from(this)
.inflateTransition(R.transition.media_details));
movieDetailsFragment.setEnterTransition(
TransitionInflater.from(this)
.inflateTransition(R.transition.media_details));
movieDetailsFragment.setReturnTransition(null);
Transition changeImageTransition = TransitionInflater.from(
this).inflateTransition(R.transition.change_image);
Transition changeImageTransition =
TransitionInflater.from(this).inflateTransition(R.transition.change_image);
movieDetailsFragment.setSharedElementReturnTransition(changeImageTransition);
movieDetailsFragment.setSharedElementEnterTransition(changeImageTransition);
fragTrans.replace(R.id.fragment_container, movieDetailsFragment)
.addToBackStack(null)
.addSharedElement(vh.artView, vh.artView.getTransitionName())
.commit();
fragTrans.addSharedElement(vh.artView, vh.artView.getTransitionName());
} else {
MovieDetailsFragment movieDetailsFragment = MovieDetailsFragment.newInstance(vh);
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
fragTrans.setCustomAnimations(R.anim.fragment_details_enter, 0,
R.anim.fragment_list_popenter, 0);
fragTrans.replace(R.id.fragment_container, movieDetailsFragment)
.addToBackStack(null)
.commit();
}
fragTrans.replace(R.id.fragment_container, movieDetailsFragment)
.addToBackStack(null)
.commit();
setupActionBar(selectedMovieTitle);
}

View File

@ -16,28 +16,49 @@
package org.xbmc.kore.ui;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.provider.BaseColumns;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.astuetz.PagerSlidingTabStrip;
import android.view.ViewTreeObserver;
import android.widget.GridLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import org.xbmc.kore.R;
import org.xbmc.kore.Settings;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
import org.xbmc.kore.jsonrpc.type.VideoType;
import org.xbmc.kore.provider.MediaContract;
import org.xbmc.kore.service.library.LibrarySyncService;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.TabsAdapter;
import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
import java.util.ArrayList;
import butterknife.ButterKnife;
import butterknife.InjectView;
/**
* Container for the TV Show overview and Episodes list
* Presents a TV Show overview
*/
public class TVShowDetailsFragment extends Fragment {
public class TVShowDetailsFragment extends AbstractDetailsFragment
implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String TAG = LogUtils.makeLogTag(TVShowDetailsFragment.class);
public static final String POSTER_TRANS_NAME = "POSTER_TRANS_NAME";
@ -51,13 +72,54 @@ public class TVShowDetailsFragment extends Fragment {
public static final String BUNDLE_KEY_PLOT = "plot";
public static final String BUNDLE_KEY_GENRES = "genres";
public interface OnSeasonSelectedListener {
void onSeasonSelected(int tvshowId, int season);
}
// Activity listener
private OnSeasonSelectedListener listenerActivity;
// Loader IDs
private static final int LOADER_TVSHOW = 0,
LOADER_SEASONS = 1,
LOADER_CAST = 2;
// Displayed movie id
private int tvshowId = -1;
private String tvshowTitle;
private TabsAdapter tabsAdapter;
// Controls whether a automatic sync refresh has been issued for this show
private static boolean hasIssuedOutdatedRefresh = false;
@InjectView(R.id.pager_tab_strip) PagerSlidingTabStrip pagerTabStrip;
@InjectView(R.id.pager) ViewPager viewPager;
@InjectView(R.id.swipe_refresh_layout) SwipeRefreshLayout swipeRefreshLayout;
// // Buttons
// @InjectView(R.id.go_to_imdb) ImageButton imdbButton;
// Detail views
@InjectView(R.id.media_panel) ScrollView mediaPanel;
@InjectView(R.id.art) ImageView mediaArt;
@InjectView(R.id.poster) ImageView mediaPoster;
@InjectView(R.id.media_title) TextView mediaTitle;
@InjectView(R.id.media_undertitle) TextView mediaUndertitle;
@InjectView(R.id.rating) TextView mediaRating;
@InjectView(R.id.max_rating) TextView mediaMaxRating;
@InjectView(R.id.premiered) TextView mediaPremiered;
@InjectView(R.id.genres) TextView mediaGenres;
@InjectView(R.id.media_description) TextView mediaDescription;
@InjectView(R.id.cast_list) GridLayout videoCastList;
@InjectView(R.id.seasons_title) TextView seasonsListTitle;
@InjectView(R.id.seasons_list) GridLayout seasonsList;
@InjectView(R.id.media_description_container) LinearLayout mediaDescriptionContainer;
@InjectView(R.id.show_all) ImageView mediaShowAll;
private boolean isDescriptionExpanded = false;
/**
* Create a new instance of this, initialized to show tvshowId
@ -84,61 +146,452 @@ public class TVShowDetailsFragment extends Fragment {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@TargetApi(21)
protected View createView(LayoutInflater inflater, ViewGroup container) {
Bundle bundle = getArguments();
tvshowId = bundle.getInt(BUNDLE_KEY_TVSHOWID, -1);
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
tvshowId = getArguments().getInt(BUNDLE_KEY_TVSHOWID, -1);
if ((container == null) || (tvshowId == -1)) {
// We're not being shown or there's nothing to show
if (tvshowId == -1) {
// There's nothing to show
return null;
}
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_default_view_pager, container, false);
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_tvshow_overview, container, false);
ButterKnife.inject(this, root);
long baseFragmentId = tvshowId * 10;
tabsAdapter = new TabsAdapter(getActivity(), getChildFragmentManager())
.addTab(TVShowOverviewFragment.class, getArguments(), R.string.tvshow_overview,
baseFragmentId)
.addTab(TVShowEpisodeListFragment.class, getArguments(),
R.string.tvshow_episodes, baseFragmentId + 1);
//UIUtils.setSwipeRefreshLayoutColorScheme(swipeRefreshLayout);
viewPager.setAdapter(tabsAdapter);
pagerTabStrip.setViewPager(viewPager);
// Setup dim the fanart when scroll changes. Full dim on 4 * iconSize dp
Resources resources = getActivity().getResources();
final int pixelsToTransparent = 4 * resources.getDimensionPixelSize(R.dimen.default_icon_size);
mediaPanel.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
@Override
public void onScrollChanged() {
float y = mediaPanel.getScrollY();
float newAlpha = Math.min(1, Math.max(0, 1 - (y / pixelsToTransparent)));
mediaArt.setAlpha(newAlpha);
}
});
tvshowTitle = bundle.getString(BUNDLE_KEY_TITLE);
mediaTitle.setText(tvshowTitle);
setMediaUndertitle(bundle.getInt(BUNDLE_KEY_EPISODE), bundle.getInt(BUNDLE_KEY_WATCHEDEPISODES));
setMediaPremiered(bundle.getString(BUNDLE_KEY_PREMIERED), bundle.getString(BUNDLE_KEY_STUDIO));
mediaGenres.setText(bundle.getString(BUNDLE_KEY_GENRES));
setMediaRating(bundle.getDouble(BUNDLE_KEY_RATING));
mediaDescription.setText(bundle.getString(BUNDLE_KEY_PLOT));
if(Utils.isLollipopOrLater()) {
mediaPoster.setTransitionName(getArguments().getString(POSTER_TRANS_NAME));
}
// Pad main content view to overlap with bottom system bar
// UIUtils.setPaddingForSystemBars(getActivity(), mediaPanel, false, false, true);
// mediaPanel.setClipToPadding(false);
return root;
}
@Override
protected String getSyncType() {
return LibrarySyncService.SYNC_SINGLE_TVSHOW;
}
@Override
protected String getSyncID() {
return LibrarySyncService.SYNC_TVSHOWID;
}
@Override
protected int getSyncItemID() {
return tvshowId;
}
@Override
protected SwipeRefreshLayout getSwipeRefreshLayout() {
return swipeRefreshLayout;
}
@Override
protected void onDownload() { }
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(false);
hasIssuedOutdatedRefresh = false;
// Start the loaders
getLoaderManager().initLoader(LOADER_TVSHOW, null, this);
getLoaderManager().initLoader(LOADER_SEASONS, null, this);
getLoaderManager().initLoader(LOADER_CAST, null, this);
}
public Fragment getCurrentTabFragment() {
return tabsAdapter.getItem(viewPager.getCurrentItem());
@Override
public void onAttach(Context activity) {
super.onAttach(activity);
try {
listenerActivity = (OnSeasonSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnSeasonSelectedListener");
}
}
@Override
public void onDetach() {
super.onDetach();
listenerActivity = null;
}
@Override
protected void onSyncProcessEnded(MediaSyncEvent event) {
if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
getLoaderManager().restartLoader(LOADER_TVSHOW, null, this);
getLoaderManager().restartLoader(LOADER_SEASONS, null, this);
getLoaderManager().restartLoader(LOADER_CAST, null, this);
}
}
/**
* Loader callbacks
*/
/** {@inheritDoc} */
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
Uri uri;
switch (i) {
case LOADER_TVSHOW:
uri = MediaContract.TVShows.buildTVShowUri(getHostInfo().getId(), tvshowId);
return new CursorLoader(getActivity(), uri,
TVShowDetailsQuery.PROJECTION, null, null, null);
case LOADER_SEASONS:
// Load seasons
uri = MediaContract.Seasons.buildTVShowSeasonsListUri(getHostInfo().getId(), tvshowId);
return new CursorLoader(getActivity(), uri,
SeasonsListQuery.PROJECTION, null, null, SeasonsListQuery.SORT);
case LOADER_CAST:
uri = MediaContract.TVShowCast.buildTVShowCastListUri(getHostInfo().getId(), tvshowId);
return new CursorLoader(getActivity(), uri,
TVShowCastListQuery.PROJECTION, null, null, TVShowCastListQuery.SORT);
default:
return null;
}
}
/** {@inheritDoc} */
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
LogUtils.LOGD(TAG, "onLoadFinished");
if (cursor != null && cursor.getCount() > 0) {
switch (cursorLoader.getId()) {
case LOADER_TVSHOW:
displayTVShowDetails(cursor);
checkOutdatedTVShowDetails(cursor);
break;
case LOADER_SEASONS:
displaySeasonList(cursor);
break;
case LOADER_CAST:
displayCastList(cursor);
break;
}
}
}
/** {@inheritDoc} */
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
// Release loader's data
}
// /**
// * Callbacks for button bar
// */
// @OnClick(R.id.go_to_imdb)
// public void onImdbClicked(View v) {
// String imdbNumber = (String)v.getTag();
//
// if (imdbNumber != null) {
// Utils.openImdbForMovie(getActivity(), imdbNumber);
// }
// }
/**
* Display the tv show details
*
* @param cursor Cursor with the data
*/
private void displayTVShowDetails(Cursor cursor) {
LogUtils.LOGD(TAG, "displayTVShowDetails");
cursor.moveToFirst();
tvshowTitle = cursor.getString(TVShowDetailsQuery.TITLE);
mediaTitle.setText(tvshowTitle);
int numEpisodes = cursor.getInt(TVShowDetailsQuery.EPISODE),
watchedEpisodes = cursor.getInt(TVShowDetailsQuery.WATCHEDEPISODES);
setMediaUndertitle(numEpisodes, watchedEpisodes);
setMediaPremiered(cursor.getString(TVShowDetailsQuery.PREMIERED), cursor.getString(TVShowDetailsQuery.STUDIO));
mediaGenres.setText(cursor.getString(TVShowDetailsQuery.GENRES));
setMediaRating(cursor.getDouble(TVShowDetailsQuery.RATING));
mediaDescription.setText(cursor.getString(TVShowDetailsQuery.PLOT));
Resources resources = getActivity().getResources();
final int maxLines = resources.getInteger(R.integer.description_max_lines);
TypedArray styledAttributes = getActivity().getTheme().obtainStyledAttributes(new int[] {
R.attr.iconExpand,
R.attr.iconCollapse
});
final int iconCollapseResId =
styledAttributes.getResourceId(styledAttributes.getIndex(0), R.drawable.ic_expand_less_white_24dp);
final int iconExpandResId =
styledAttributes.getResourceId(styledAttributes.getIndex(1), R.drawable.ic_expand_more_white_24dp);
styledAttributes.recycle();
mediaDescriptionContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!isDescriptionExpanded) {
mediaDescription.setMaxLines(Integer.MAX_VALUE);
mediaShowAll.setImageResource(iconExpandResId);
} else {
mediaDescription.setMaxLines(maxLines);
mediaShowAll.setImageResource(iconCollapseResId);
}
isDescriptionExpanded = !isDescriptionExpanded;
}
});
// // IMDB button
// imdbButton.setTag(cursor.getString(TVShowDetailsQuery.IMDBNUMBER));
// Images
DisplayMetrics displayMetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int posterWidth = resources.getDimensionPixelOffset(R.dimen.now_playing_poster_width);
int posterHeight = resources.getDimensionPixelOffset(R.dimen.now_playing_poster_height);
UIUtils.loadImageWithCharacterAvatar(getActivity(), getHostManager(),
cursor.getString(TVShowDetailsQuery.THUMBNAIL), tvshowTitle,
mediaPoster, posterWidth, posterHeight);
int artHeight = resources.getDimensionPixelOffset(R.dimen.now_playing_art_height);
UIUtils.loadImageIntoImageview(getHostManager(),
cursor.getString(TVShowDetailsQuery.FANART),
mediaArt, displayMetrics.widthPixels, artHeight);
}
private void setMediaUndertitle(int numEpisodes, int watchedEpisodes) {
String episodes = String.format(getString(R.string.num_episodes),
numEpisodes, numEpisodes - watchedEpisodes);
mediaUndertitle.setText(episodes);
}
private void setMediaPremiered(String premiered, String studio) {
mediaPremiered.setText(String.format(getString(R.string.premiered), premiered) + " | " + studio);
}
private void setMediaRating(double rating) {
if (rating > 0) {
mediaRating.setVisibility(View.VISIBLE);
mediaMaxRating.setVisibility(View.VISIBLE);
mediaRating.setText(String.format("%01.01f", rating));
mediaMaxRating.setText(getString(R.string.max_rating_video));
} else {
mediaRating.setVisibility(View.INVISIBLE);
mediaMaxRating.setVisibility(View.INVISIBLE);
}
}
/**
* Display the cast details
*
* @param cursor Cursor with the data
*/
private void displayCastList(Cursor cursor) {
// Transform the cursor into a List<VideoType.Cast>
if (cursor.moveToFirst()) {
ArrayList<VideoType.Cast> castArrayList = new ArrayList<VideoType.Cast>(cursor.getCount());
do {
castArrayList.add(new VideoType.Cast(cursor.getString(TVShowCastListQuery.NAME),
cursor.getInt(TVShowCastListQuery.ORDER),
cursor.getString(TVShowCastListQuery.ROLE),
cursor.getString(TVShowCastListQuery.THUMBNAIL)));
} while (cursor.moveToNext());
UIUtils.setupCastInfo(getActivity(), castArrayList, videoCastList,
AllCastActivity.buildLaunchIntent(getActivity(), tvshowTitle, castArrayList));
}
}
/**
* Display the seasons list
*
* @param cursor Cursor with the data
*/
private void displaySeasonList(Cursor cursor) {
if (cursor.moveToFirst()) {
HostManager hostManager = HostManager.getInstance(getActivity());
View.OnClickListener seasonListClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
listenerActivity.onSeasonSelected(tvshowId, (int)v.getTag());
}
};
// Get the art dimensions
Resources resources = getActivity().getResources();
int artWidth = (int)(resources.getDimension(R.dimen.seasonlist_art_width) /
UIUtils.IMAGE_RESIZE_FACTOR);
int artHeight = (int)(resources.getDimension(R.dimen.seasonlist_art_heigth) /
UIUtils.IMAGE_RESIZE_FACTOR);
seasonsList.removeAllViews();
do {
int seasonNumber = cursor.getInt(SeasonsListQuery.SEASON);
String thumbnail = cursor.getString(SeasonsListQuery.THUMBNAIL);
int numEpisodes = cursor.getInt(SeasonsListQuery.EPISODE);
int watchedEpisodes = cursor.getInt(SeasonsListQuery.WATCHEDEPISODES);
View seasonView = LayoutInflater.from(getActivity()).inflate(R.layout.grid_item_season, seasonsList, false);
ImageView seasonPictureView = (ImageView) seasonView.findViewById(R.id.art);
TextView seasonNumberView = (TextView) seasonView.findViewById(R.id.season);
TextView seasonEpisodesView = (TextView) seasonView.findViewById(R.id.episodes);
seasonNumberView.setText(String.format(getActivity().getString(R.string.season_number), seasonNumber));
seasonEpisodesView.setText(String.format(getActivity().getString(R.string.num_episodes),
numEpisodes, numEpisodes - watchedEpisodes));
UIUtils.loadImageWithCharacterAvatar(getActivity(), hostManager,
thumbnail,
String.valueOf(seasonNumber),
seasonPictureView, artWidth, artHeight);
seasonView.setTag(seasonNumber);
seasonView.setOnClickListener(seasonListClickListener);
seasonsList.addView(seasonView);
} while (cursor.moveToNext());
} else {
// No seasons, hide views
seasonsListTitle.setVisibility(View.GONE);
seasonsList.setVisibility(View.INVISIBLE);
}
}
/**
* Checks wether we should refresh the TV Show details with the info on XBMC
* The details will be updated if the last update is older than what is configured in the
* settings
*
* @param cursor Cursor with the data
*/
private void checkOutdatedTVShowDetails(Cursor cursor) {
if (hasIssuedOutdatedRefresh)
return;
cursor.moveToFirst();
long lastUpdated = cursor.getLong(TVShowDetailsQuery.UPDATED);
if (System.currentTimeMillis() > lastUpdated + Settings.DB_UPDATE_INTERVAL) {
// Trigger a silent refresh
hasIssuedOutdatedRefresh = true;
startSync(true);
}
}
/**
* Returns the shared element if visible
* @return View if visible, null otherwise
*/
public View getSharedElement() {
View view = getView();
if (view == null)
return null;
//Note: this works as R.id.poster is only used in TVShowOverviewFragment.
//If the same id is used in other fragments in the TabsAdapter we
//need to check which fragment is currently displayed
View artView = view.findViewById(R.id.poster);
View scrollView = view.findViewById(R.id.media_panel);
if (( artView != null ) &&
( scrollView != null ) &&
UIUtils.isViewInBounds(scrollView, artView)) {
return artView;
if (UIUtils.isViewInBounds(mediaPanel, mediaPoster)) {
return mediaPoster;
}
return null;
}
/**
* TV Show details query parameters.
*/
private interface TVShowDetailsQuery {
String[] PROJECTION = {
BaseColumns._ID,
MediaContract.TVShows.TITLE,
MediaContract.TVShows.THUMBNAIL,
MediaContract.TVShows.FANART,
MediaContract.TVShows.PREMIERED,
MediaContract.TVShows.STUDIO,
MediaContract.TVShows.EPISODE,
MediaContract.TVShows.WATCHEDEPISODES,
MediaContract.TVShows.RATING,
MediaContract.TVShows.PLOT,
MediaContract.TVShows.PLAYCOUNT,
MediaContract.TVShows.IMDBNUMBER,
MediaContract.TVShows.GENRES,
MediaContract.SyncColumns.UPDATED,
};
int ID = 0;
int TITLE = 1;
int THUMBNAIL = 2;
int FANART = 3;
int PREMIERED = 4;
int STUDIO = 5;
int EPISODE = 6;
int WATCHEDEPISODES = 7;
int RATING = 8;
int PLOT = 9;
int PLAYCOUNT = 10;
int IMDBNUMBER = 11;
int GENRES = 12;
int UPDATED = 13;
}
/**
* Seasons list query parameters.
*/
private interface SeasonsListQuery {
String[] PROJECTION = {
BaseColumns._ID,
MediaContract.Seasons.SEASON,
MediaContract.Seasons.THUMBNAIL,
MediaContract.Seasons.EPISODE,
MediaContract.Seasons.WATCHEDEPISODES
};
String SORT = MediaContract.Seasons.SEASON + " ASC";
int ID = 0;
int SEASON = 1;
int THUMBNAIL = 2;
int EPISODE = 3;
int WATCHEDEPISODES = 4;
}
/**
* Movie cast list query parameters.
*/
public interface TVShowCastListQuery {
String[] PROJECTION = {
BaseColumns._ID,
MediaContract.TVShowCast.NAME,
MediaContract.TVShowCast.ORDER,
MediaContract.TVShowCast.ROLE,
MediaContract.TVShowCast.THUMBNAIL,
};
String SORT = MediaContract.TVShowCast.ORDER + " ASC";
int ID = 0;
int NAME = 1;
int ORDER = 2;
int ROLE = 3;
int THUMBNAIL = 4;
}
}

View File

@ -15,7 +15,7 @@
*/
package org.xbmc.kore.ui;
import android.app.Activity;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
@ -25,198 +25,128 @@ import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.BaseColumns;
import android.support.v4.app.LoaderManager;
import android.support.annotation.Nullable;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SwipeRefreshLayout;
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.CursorTreeAdapter;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.CursorAdapter;
import org.xbmc.kore.R;
import org.xbmc.kore.Settings;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
import org.xbmc.kore.jsonrpc.type.PlaylistType;
import org.xbmc.kore.provider.MediaContract;
import org.xbmc.kore.service.library.LibrarySyncService;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.MediaPlayerUtils;
import org.xbmc.kore.utils.UIUtils;
import java.util.HashMap;
import butterknife.ButterKnife;
import butterknife.InjectView;
import org.xbmc.kore.utils.Utils;
/**
* Presents a list of episodes for a TV show
* Presents a list of episodes for a TV show season
*/
public class TVShowEpisodeListFragment extends AbstractDetailsFragment
implements LoaderManager.LoaderCallbacks<Cursor> {
public class TVShowEpisodeListFragment extends AbstractCursorListFragment {
private static final String TAG = LogUtils.makeLogTag(TVShowEpisodeListFragment.class);
public interface OnEpisodeSelectedListener {
public void onEpisodeSelected(int tvshowId, int episodeId);
void onEpisodeSelected(EpisodeViewHolder vh);
}
public static final String TVSHOWID = "tvshow_id";
public static final String SEASON = "season";
private final String BUNDLE_SAVEDINSTANCE_LISTPOSITION = "lposition";
private final String BUNDLE_SAVEDINSTANCE_ITEMPOSITION = "iposition";
private final String BUNDLE_SAVEDINSTANCE_GROUPSEXPANDED = "groupsexpanded";
// Loader IDs. Must be -1 to differentiate from group position
private static final int LOADER_SEASONS = -1;
private int listPosition = 0;
private int itemPosition = 0;
private HashMap<Integer, Boolean> groupsExpanded = new HashMap<>();
private HashMap<Integer, Boolean> childCursorsLoading;
private boolean isReturning; // used to determine if we are returning to this fragment and need to restore the state
public static final String TVSHOWSEASON = "season";
// Displayed show id
private int tvshowId = -1;
private int tvshowSeason = -1;
// Activity listener
private OnEpisodeSelectedListener listenerActivity;
private SeasonsEpisodesAdapter adapter;
/**
* Create a new instance of this, initialized to show tvshowId
*/
@TargetApi(21)
public static TVShowEpisodeListFragment newInstance(int tvshowId, int season) {
TVShowEpisodeListFragment fragment = new TVShowEpisodeListFragment();
@InjectView(R.id.list) ExpandableListView seasonsEpisodesListView;
@InjectView(R.id.swipe_refresh_layout) SwipeRefreshLayout swipeRefreshLayout;
@InjectView(android.R.id.empty) TextView emptyView;
Bundle args = new Bundle();
args.putInt(TVSHOWID, tvshowId);
args.putInt(TVSHOWSEASON, season);
fragment.setArguments(args);
return fragment;
}
@Override
protected View createView(LayoutInflater inflater, ViewGroup container) {
protected String getListSyncType() { return LibrarySyncService.SYNC_SINGLE_TVSHOW; }
@Override
protected String getSyncID() { return LibrarySyncService.SYNC_TVSHOWID; };
@Override
protected int getSyncItemID() { return tvshowId; };
@TargetApi(16) @Nullable @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, container, savedInstanceState);
tvshowId = getArguments().getInt(TVSHOWID, -1);
if (tvshowId == -1) {
tvshowSeason = getArguments().getInt(TVSHOWSEASON, -1);
if ((tvshowId == -1) || (tvshowSeason == -1)) {
// There's nothing to show
return null;
}
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_tvshow_episodes_list, container, false);
ButterKnife.inject(this, root);
// Configure the adapter and start the loader
adapter = new SeasonsEpisodesAdapter(getActivity());
seasonsEpisodesListView.setAdapter(adapter);
return root;
}
@Override
protected String getSyncType() {
return LibrarySyncService.SYNC_SINGLE_TVSHOW;
protected void onListItemClicked(View view) {
// Get the movie id from the tag
EpisodeViewHolder tag = (EpisodeViewHolder) view.getTag();
// Notify the activity
listenerActivity.onEpisodeSelected(tag);
}
@Override
protected CursorAdapter createAdapter() {
return new SeasonsEpisodesAdapter(getActivity());
}
@Override
protected String getSyncID() {
return LibrarySyncService.SYNC_TVSHOWID;
}
protected CursorLoader createCursorLoader() {
HostInfo hostInfo = HostManager.getInstance(getActivity()).getHostInfo();
Uri uri = MediaContract.Episodes.buildTVShowSeasonEpisodesListUri(hostInfo.getId(), tvshowId, tvshowSeason);
@Override
protected int getSyncItemID() {
return tvshowId;
}
@Override
protected SwipeRefreshLayout getSwipeRefreshLayout() {
return swipeRefreshLayout;
}
@Override
protected void onDownload() {
}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
seasonsEpisodesListView.setEmptyView(emptyView);
seasonsEpisodesListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
if (parent.isGroupExpanded(groupPosition)) {
parent.collapseGroup(groupPosition);
groupsExpanded.remove(groupPosition);
} else {
parent.expandGroup(groupPosition);
groupsExpanded.put(groupPosition, true);
}
return true;
}
});
seasonsEpisodesListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
// Get the movie id from the tag
EpisodeViewHolder tag = (EpisodeViewHolder) v.getTag();
// Notify the activity
listenerActivity.onEpisodeSelected(tvshowId, tag.episodeId);
return true;
}
});
setHasOptionsMenu(true);
if (savedInstanceState != null) {
listPosition = savedInstanceState.getInt(BUNDLE_SAVEDINSTANCE_LISTPOSITION, 0);
itemPosition = savedInstanceState.getInt(BUNDLE_SAVEDINSTANCE_ITEMPOSITION, 0);
groupsExpanded = (HashMap) savedInstanceState.getSerializable(BUNDLE_SAVEDINSTANCE_GROUPSEXPANDED);
isReturning = true;
// Filters
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
StringBuilder selection = new StringBuilder();
if (preferences.getBoolean(Settings.KEY_PREF_TVSHOWS_FILTER_HIDE_WATCHED, Settings.DEFAULT_PREF_TVSHOWS_FILTER_HIDE_WATCHED)) {
selection.append(MediaContract.EpisodesColumns.PLAYCOUNT)
.append("=0");
}
initLoader();
return new CursorLoader(getActivity(), uri,
EpisodesListQuery.PROJECTION, selection.toString(), null, EpisodesListQuery.SORT);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
public void onAttach(Context context) {
super.onAttach(context);
try {
listenerActivity = (OnEpisodeSelectedListener) activity;
listenerActivity = (OnEpisodeSelectedListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnEpisodeSelectedListener");
throw new ClassCastException(context.toString() + " must implement OnEpisodeSelectedListener");
}
}
@Override
public void onPause() {
super.onPause();
//Save scroll position
listPosition = seasonsEpisodesListView.getFirstVisiblePosition();
View itemView = seasonsEpisodesListView.getChildAt(0);
if (itemView != null) {
itemPosition = itemView.getTop();
} else {
itemPosition = 0;
}
isReturning = true;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(BUNDLE_SAVEDINSTANCE_ITEMPOSITION, itemPosition);
outState.putInt(BUNDLE_SAVEDINSTANCE_LISTPOSITION, listPosition);
outState.putSerializable(BUNDLE_SAVEDINSTANCE_GROUPSEXPANDED, groupsExpanded);
}
@Override
public void onDetach() {
super.onDetach();
@ -250,7 +180,7 @@ public class TVShowEpisodeListFragment extends AbstractDetailsFragment
preferences.edit()
.putBoolean(Settings.KEY_PREF_TVSHOW_EPISODES_FILTER_HIDE_WATCHED, item.isChecked())
.apply();
getLoaderManager().restartLoader(LOADER_SEASONS, null, this);
refreshList();
break;
default:
break;
@ -259,161 +189,6 @@ public class TVShowEpisodeListFragment extends AbstractDetailsFragment
return super.onOptionsItemSelected(item);
}
@Override
protected void onSyncProcessEnded(MediaSyncEvent event) {
if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
getLoaderManager().restartLoader(LOADER_SEASONS, null, this);
}
}
/**
* Loader callbacks
*/
/** {@inheritDoc} */
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle bundle) {
if (!isAdded()) {
LogUtils.LOGD(TAG, "Trying to create a loader, but the fragment isn't added. " +
"Loader Id: " + id);
return null;
}
Uri uri;
StringBuilder selection = new StringBuilder();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
boolean tvshowEpisodesFilterHideWatched =
preferences.getBoolean(Settings.KEY_PREF_TVSHOW_EPISODES_FILTER_HIDE_WATCHED, Settings.DEFAULT_PREF_TVSHOW_EPISODES_FILTER_HIDE_WATCHED);
switch (id) {
case LOADER_SEASONS:
// Load seasons
uri = MediaContract.Seasons.buildTVShowSeasonsListUri(getHostInfo().getId(), tvshowId);
// Filters
if (tvshowEpisodesFilterHideWatched) {
selection.append(MediaContract.SeasonsColumns.WATCHEDEPISODES)
.append("!=")
.append(MediaContract.SeasonsColumns.EPISODE);
}
return new CursorLoader(getActivity(), uri,
SeasonsListQuery.PROJECTION, selection.toString(), null, SeasonsListQuery.SORT);
default:
// Load episodes for a season. Season is in bundle
int season = bundle.getInt(SEASON);
uri = MediaContract.Episodes.buildTVShowSeasonEpisodesListUri(getHostInfo().getId(), tvshowId, season);
// Filters
if (tvshowEpisodesFilterHideWatched) {
selection.append(MediaContract.EpisodesColumns.PLAYCOUNT)
.append("=0");
}
return new CursorLoader(getActivity(), uri,
EpisodesListQuery.PROJECTION, selection.toString(), null, EpisodesListQuery.SORT);
}
}
/** {@inheritDoc} */
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
switch (cursorLoader.getId()) {
case LOADER_SEASONS:
adapter.setGroupCursor(cursor);
setState(cursor);
// To prevent the empty text from appearing on the first load, set it now
emptyView.setText(getString(R.string.no_episodes_found));
break;
default:
// Check if the group cursor is set before setting the children cursor
// Somehow, when popping the back stack, the children cursor are reloaded first...
if (adapter.getCursor() != null) {
int id = cursorLoader.getId();
adapter.setChildrenCursor(id, cursor);
childCursorsLoading.remove(id);
if (isReturning && childCursorsLoading.isEmpty()) {
isReturning = false;
//All previous expanded child cursors loaded. Now we can finally restore the list position
seasonsEpisodesListView.setSelectionFromTop(listPosition, itemPosition);
}
}
break;
}
}
/** {@inheritDoc} */
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
switch (cursorLoader.getId()) {
case LOADER_SEASONS:
adapter.setGroupCursor(null);
break;
default:
// Check if the group cursor is set before setting the children cursor
// Somehow, when popping the back stack, the children cursor are reloaded first...
if (adapter.getCursor() != null) {
try {
adapter.setChildrenCursor(cursorLoader.getId(), null);
} catch (NullPointerException exc) {
// Errrr... Adapter expired?
LogUtils.LOGW(TAG, "Adapter expired.");
}
}
break;
}
}
private void setState(Cursor cursor) {
if (cursor.getCount() == 1) {
seasonsEpisodesListView.expandGroup(0);
} else if (cursor.getCount() > 0) {
cursor.moveToFirst();
do {
int position = cursor.getPosition();
// Expand the first season that has unseen episodes
int unwatched = cursor.getInt(SeasonsListQuery.EPISODE) - cursor.getInt(SeasonsListQuery.WATCHEDEPISODES);
if (groupsExpanded.isEmpty() && (unwatched > 0)) {
seasonsEpisodesListView.expandGroup(position);
groupsExpanded.put(position, true);
break;
}
if (groupsExpanded.get(position) != null) {
seasonsEpisodesListView.expandGroup(position);
}
} while (cursor.moveToNext());
}
}
private void initLoader() {
childCursorsLoading = new HashMap<>();
for (int id : groupsExpanded.keySet()) {
childCursorsLoading.put(id, true);
}
getLoaderManager().initLoader(LOADER_SEASONS, null, this);
}
/**
* Seasons list query parameters.
*/
private interface SeasonsListQuery {
String[] PROJECTION = {
BaseColumns._ID,
MediaContract.Seasons.SEASON,
MediaContract.Seasons.THUMBNAIL,
MediaContract.Seasons.EPISODE,
MediaContract.Seasons.WATCHEDEPISODES
};
String SORT = MediaContract.Seasons.SEASON + " ASC";
final int ID = 0;
final int SEASON = 1;
final int THUMBNAIL = 2;
final int EPISODE = 3;
final int WATCHEDEPISODES = 4;
}
/**
* Episodes list query parameters.
*/
@ -431,111 +206,76 @@ public class TVShowEpisodeListFragment extends AbstractDetailsFragment
String SORT = MediaContract.Episodes.EPISODE + " ASC";
final int ID = 0;
final int EPISODEID = 1;
final int EPISODE = 2;
final int THUMBNAIL = 3;
final int PLAYCOUNT = 4;
final int TITLE = 5;
final int RUNTIME = 6;
final int FIRSTAIRED = 7;
int ID = 0;
int EPISODEID = 1;
int EPISODE = 2;
int THUMBNAIL = 3;
int PLAYCOUNT = 4;
int TITLE = 5;
int RUNTIME = 6;
int FIRSTAIRED = 7;
}
/**
* Adapter for the {@link android.widget.ExpandableListView}
* Manages the seasons and episodes list
*/
private class SeasonsEpisodesAdapter extends CursorTreeAdapter {
private int themeAccentColor;
private int iconCollapseResId,
iconExpandResId;
private class SeasonsEpisodesAdapter extends CursorAdapter {
private HostManager hostManager;
private int artWidth, artHeight;
private int themeAccentColor;
public SeasonsEpisodesAdapter(Context context) {
// Cursor will be set vir CursorLoader
super(null, context);
super(context, null, false);
// Get the default accent color
Resources.Theme theme = context.getTheme();
TypedArray styledAttributes = theme.obtainStyledAttributes(new int[] {
R.attr.colorAccent,
R.attr.iconCollapse,
R.attr.iconExpand,
R.attr.colorAccent
});
themeAccentColor = styledAttributes.getColor(styledAttributes.getIndex(0), getResources().getColor(R.color.accent_default));
iconCollapseResId = styledAttributes.getResourceId(styledAttributes.getIndex(1), R.drawable.ic_expand_less_white_24dp);
iconExpandResId = styledAttributes.getResourceId(styledAttributes.getIndex(2), R.drawable.ic_expand_more_white_24dp);
styledAttributes.recycle();
this.hostManager = HostManager.getInstance(context);
// Get the art dimensions
Resources resources = context.getResources();
artWidth = (int)(resources.getDimension(R.dimen.seasonlist_art_width) /
artWidth = (int)(resources.getDimension(R.dimen.episodelist_art_width) /
UIUtils.IMAGE_RESIZE_FACTOR);
artHeight = (int)(resources.getDimension(R.dimen.seasonlist_art_heigth) /
artHeight = (int)(resources.getDimension(R.dimen.episodelist_art_heigth) /
UIUtils.IMAGE_RESIZE_FACTOR);
}
/** {@inheritDoc} */
@Override
public View newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.list_item_season, parent, false);
}
@Override
public View newChildView(Context context, Cursor cursor, boolean isLastChild, ViewGroup parent) {
public View newView(Context context, final Cursor cursor, ViewGroup parent) {
final View view = LayoutInflater.from(context)
.inflate(R.layout.list_item_episode, parent, false);
// Setup View holder pattern
EpisodeViewHolder viewHolder = new EpisodeViewHolder();
viewHolder.container = (RelativeLayout)view.findViewById(R.id.container);
viewHolder.titleView = (TextView)view.findViewById(R.id.title);
viewHolder.detailsView = (TextView)view.findViewById(R.id.details);
viewHolder.episodenumberView = (TextView)view.findViewById(R.id.episode_number);
viewHolder.contextMenuView = (ImageView)view.findViewById(R.id.list_context_menu);
viewHolder.checkmarkView = (ImageView)view.findViewById(R.id.checkmark);
viewHolder.artView = (ImageView)view.findViewById(R.id.art);
view.setTag(viewHolder);
return view;
}
/** {@inheritDoc} */
@Override
public void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) {
TextView seasonView = (TextView)view.findViewById(R.id.season);
TextView episodesView = (TextView)view.findViewById(R.id.episodes);
ImageView artView = (ImageView)view.findViewById(R.id.art);
seasonView.setText(String.format(context.getString(R.string.season_number),
cursor.getInt(SeasonsListQuery.SEASON)));
int numEpisodes = cursor.getInt(SeasonsListQuery.EPISODE),
watchedEpisodes = cursor.getInt(SeasonsListQuery.WATCHEDEPISODES);
episodesView.setText(String.format(context.getString(R.string.num_episodes),
numEpisodes, numEpisodes - watchedEpisodes));
UIUtils.loadImageWithCharacterAvatar(context, hostManager,
cursor.getString(SeasonsListQuery.THUMBNAIL),
String.valueOf(cursor.getInt(SeasonsListQuery.SEASON)),
artView, artWidth, artHeight);
ImageView indicator = (ImageView)view.findViewById(R.id.status_indicator);
if (isExpanded) {
indicator.setImageResource(iconCollapseResId);
} else {
indicator.setImageResource(iconExpandResId);
}
}
@Override
public void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) {
public void bindView(View view, Context context, Cursor cursor) {
final EpisodeViewHolder viewHolder = (EpisodeViewHolder)view.getTag();
// Save the episode id
viewHolder.episodeId = cursor.getInt(EpisodesListQuery.EPISODEID);
viewHolder.title = cursor.getString(EpisodesListQuery.TITLE);
if(Utils.isLollipopOrLater()) {
viewHolder.artView.setTransitionName("a"+viewHolder.episodeId);
}
viewHolder.episodenumberView.setText(
String.format(context.getString(R.string.episode_number),
@ -555,49 +295,30 @@ public class TVShowEpisodeListFragment extends AbstractDetailsFragment
viewHolder.checkmarkView.setVisibility(View.INVISIBLE);
}
UIUtils.loadImageWithCharacterAvatar(context, hostManager,
cursor.getString(EpisodesListQuery.THUMBNAIL), viewHolder.title,
viewHolder.artView, artWidth, artHeight);
// For the popupmenu
ImageView contextMenu = (ImageView)view.findViewById(R.id.list_context_menu);
contextMenu.setTag(viewHolder);
contextMenu.setOnClickListener(contextlistItemMenuClickListener);
}
@Override
public Cursor getChildrenCursor(Cursor groupCursor) {
// Check if the fragment is attached to avoid IllegalStateException...
if (!isAdded()) return null;
// Start the episodes loader
final int season = groupCursor.getInt(SeasonsListQuery.SEASON);
Bundle bundle = new Bundle();
bundle.putInt(SEASON, season);
int groupPositon = groupCursor.getPosition();
// The season id will be passed in a bundle to the loadermanager, and the group id
// will be used as the loader's id
LoaderManager loaderManager = getLoaderManager();
if ((loaderManager.getLoader(groupPositon) == null) ||
(loaderManager.getLoader(groupPositon).isReset())) {
loaderManager.initLoader(groupPositon, bundle, TVShowEpisodeListFragment.this);
} else {
loaderManager.restartLoader(groupPositon, bundle, TVShowEpisodeListFragment.this);
}
return null;
}
}
/**
* View holder pattern, only for episodes
*/
private static class EpisodeViewHolder {
RelativeLayout container;
public static class EpisodeViewHolder {
TextView titleView;
TextView detailsView;
TextView episodenumberView;
ImageView contextMenuView;
ImageView checkmarkView;
// ImageView artView;
ImageView artView;
int episodeId;
String title;
}
private View.OnClickListener contextlistItemMenuClickListener = new View.OnClickListener() {

View File

@ -1,419 +0,0 @@
/*
* Copyright 2015 Synced Synapse. All rights reserved.
*
* 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 org.xbmc.kore.ui;
import android.annotation.TargetApi;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.GridLayout;
import android.widget.ImageView;
import android.widget.ScrollView;
import android.widget.TextView;
import org.xbmc.kore.R;
import org.xbmc.kore.Settings;
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
import org.xbmc.kore.jsonrpc.type.VideoType;
import org.xbmc.kore.provider.MediaContract;
import org.xbmc.kore.service.library.LibrarySyncService;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
import java.util.ArrayList;
import butterknife.ButterKnife;
import butterknife.InjectView;
/**
* Presents a TV Show overview
*/
public class TVShowOverviewFragment extends AbstractDetailsFragment
implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String TAG = LogUtils.makeLogTag(TVShowOverviewFragment.class);
public static final String TVSHOWID = "tvshow_id";
// Loader IDs
private static final int LOADER_TVSHOW = 0,
LOADER_CAST = 1;
// Displayed movie id
private int tvshowId = -1;
private String tvshowTitle;
private ArrayList<VideoType.Cast> castArrayList;
// Controls whether a automatic sync refresh has been issued for this show
private static boolean hasIssuedOutdatedRefresh = false;
@InjectView(R.id.swipe_refresh_layout) SwipeRefreshLayout swipeRefreshLayout;
// // Buttons
// @InjectView(R.id.go_to_imdb) ImageButton imdbButton;
// Detail views
@InjectView(R.id.media_panel) ScrollView mediaPanel;
@InjectView(R.id.art) ImageView mediaArt;
@InjectView(R.id.poster) ImageView mediaPoster;
@InjectView(R.id.media_title) TextView mediaTitle;
@InjectView(R.id.media_undertitle) TextView mediaUndertitle;
@InjectView(R.id.rating) TextView mediaRating;
@InjectView(R.id.max_rating) TextView mediaMaxRating;
@InjectView(R.id.premiered) TextView mediaPremiered;
@InjectView(R.id.genres) TextView mediaGenres;
@InjectView(R.id.media_description) TextView mediaDescription;
@InjectView(R.id.cast_list) GridLayout videoCastList;
/**
* Create a new instance of this, initialized to show tvshowId
*/
public static TVShowOverviewFragment newInstance(int tvshowId) {
TVShowOverviewFragment fragment = new TVShowOverviewFragment();
Bundle args = new Bundle();
args.putInt(TVSHOWID, tvshowId);
fragment.setArguments(args);
return fragment;
}
@Override
@TargetApi(21)
protected View createView(LayoutInflater inflater, ViewGroup container) {
Bundle bundle = getArguments();
tvshowId = bundle.getInt(TVShowDetailsFragment.BUNDLE_KEY_TVSHOWID, -1);
if (tvshowId == -1) {
// There's nothing to show
return null;
}
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_tvshow_overview, container, false);
ButterKnife.inject(this, root);
//UIUtils.setSwipeRefreshLayoutColorScheme(swipeRefreshLayout);
// Setup dim the fanart when scroll changes. Full dim on 4 * iconSize dp
Resources resources = getActivity().getResources();
final int pixelsToTransparent = 4 * resources.getDimensionPixelSize(R.dimen.default_icon_size);
mediaPanel.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
@Override
public void onScrollChanged() {
float y = mediaPanel.getScrollY();
float newAlpha = Math.min(1, Math.max(0, 1 - (y / pixelsToTransparent)));
mediaArt.setAlpha(newAlpha);
}
});
tvshowTitle = bundle.getString(TVShowDetailsFragment.BUNDLE_KEY_TITLE);
mediaTitle.setText(tvshowTitle);
setMediaUndertitle(bundle.getInt(TVShowDetailsFragment.BUNDLE_KEY_EPISODE), bundle.getInt(TVShowDetailsFragment.BUNDLE_KEY_WATCHEDEPISODES));
setMediaPremiered(bundle.getString(TVShowDetailsFragment.BUNDLE_KEY_PREMIERED), bundle.getString(TVShowDetailsFragment.BUNDLE_KEY_STUDIO));
mediaGenres.setText(bundle.getString(TVShowDetailsFragment.BUNDLE_KEY_GENRES));
setMediaRating(bundle.getDouble(TVShowDetailsFragment.BUNDLE_KEY_RATING));
mediaDescription.setText(bundle.getString(TVShowDetailsFragment.BUNDLE_KEY_PLOT));
if(Utils.isLollipopOrLater()) {
mediaPoster.setTransitionName(getArguments().getString(TVShowDetailsFragment.POSTER_TRANS_NAME));
}
// Pad main content view to overlap with bottom system bar
// UIUtils.setPaddingForSystemBars(getActivity(), mediaPanel, false, false, true);
// mediaPanel.setClipToPadding(false);
return root;
}
@Override
protected String getSyncType() {
return LibrarySyncService.SYNC_SINGLE_TVSHOW;
}
@Override
protected String getSyncID() {
return LibrarySyncService.SYNC_TVSHOWID;
}
@Override
protected int getSyncItemID() {
return tvshowId;
}
@Override
protected SwipeRefreshLayout getSwipeRefreshLayout() {
return swipeRefreshLayout;
}
@Override
protected void onDownload() {
}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
hasIssuedOutdatedRefresh = false;
// Start the loaders
getLoaderManager().initLoader(LOADER_TVSHOW, null, this);
getLoaderManager().initLoader(LOADER_CAST, null, this);
}
@Override
protected void onSyncProcessEnded(MediaSyncEvent event) {
if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
getLoaderManager().restartLoader(LOADER_TVSHOW, null, this);
getLoaderManager().restartLoader(LOADER_CAST, null, this);
}
}
/**
* Loader callbacks
*/
/** {@inheritDoc} */
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
Uri uri;
switch (i) {
case LOADER_TVSHOW:
uri = MediaContract.TVShows.buildTVShowUri(getHostInfo().getId(), tvshowId);
return new CursorLoader(getActivity(), uri,
TVShowDetailsQuery.PROJECTION, null, null, null);
case LOADER_CAST:
uri = MediaContract.TVShowCast.buildTVShowCastListUri(getHostInfo().getId(), tvshowId);
return new CursorLoader(getActivity(), uri,
TVShowCastListQuery.PROJECTION, null, null, TVShowCastListQuery.SORT);
default:
return null;
}
}
/** {@inheritDoc} */
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
LogUtils.LOGD(TAG, "onLoadFinished");
if (cursor != null && cursor.getCount() > 0) {
switch (cursorLoader.getId()) {
case LOADER_TVSHOW:
displayTVShowDetails(cursor);
checkOutdatedTVShowDetails(cursor);
break;
case LOADER_CAST:
displayCastList(cursor);
break;
}
}
}
/** {@inheritDoc} */
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
// Release loader's data
}
// /**
// * Callbacks for button bar
// */
// @OnClick(R.id.go_to_imdb)
// public void onImdbClicked(View v) {
// String imdbNumber = (String)v.getTag();
//
// if (imdbNumber != null) {
// Utils.openImdbForMovie(getActivity(), imdbNumber);
// }
// }
/**
* Display the tv show details
*
* @param cursor Cursor with the data
*/
private void displayTVShowDetails(Cursor cursor) {
LogUtils.LOGD(TAG, "displayTVShowDetails");
cursor.moveToFirst();
tvshowTitle = cursor.getString(TVShowDetailsQuery.TITLE);
mediaTitle.setText(tvshowTitle);
int numEpisodes = cursor.getInt(TVShowDetailsQuery.EPISODE),
watchedEpisodes = cursor.getInt(TVShowDetailsQuery.WATCHEDEPISODES);
setMediaUndertitle(numEpisodes, watchedEpisodes);
setMediaPremiered(cursor.getString(TVShowDetailsQuery.PREMIERED), cursor.getString(TVShowDetailsQuery.STUDIO));
mediaGenres.setText(cursor.getString(TVShowDetailsQuery.GENRES));
setMediaRating(cursor.getDouble(TVShowDetailsQuery.RATING));
mediaDescription.setText(cursor.getString(TVShowDetailsQuery.PLOT));
// // IMDB button
// imdbButton.setTag(cursor.getString(TVShowDetailsQuery.IMDBNUMBER));
// Images
Resources resources = getActivity().getResources();
DisplayMetrics displayMetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int posterWidth = resources.getDimensionPixelOffset(R.dimen.now_playing_poster_width);
int posterHeight = resources.getDimensionPixelOffset(R.dimen.now_playing_poster_height);
UIUtils.loadImageWithCharacterAvatar(getActivity(), getHostManager(),
cursor.getString(TVShowDetailsQuery.THUMBNAIL), tvshowTitle,
mediaPoster, posterWidth, posterHeight);
int artHeight = resources.getDimensionPixelOffset(R.dimen.now_playing_art_height);
UIUtils.loadImageIntoImageview(getHostManager(),
cursor.getString(TVShowDetailsQuery.FANART),
mediaArt, displayMetrics.widthPixels, artHeight);
}
private void setMediaUndertitle(int numEpisodes, int watchedEpisodes) {
String episodes = String.format(getString(R.string.num_episodes),
numEpisodes, numEpisodes - watchedEpisodes);
mediaUndertitle.setText(episodes);
}
private void setMediaPremiered(String premiered, String studio) {
mediaPremiered.setText(String.format(getString(R.string.premiered),
premiered) + " | " + studio);
}
private void setMediaRating(double rating) {
if (rating > 0) {
mediaRating.setVisibility(View.VISIBLE);
mediaMaxRating.setVisibility(View.VISIBLE);
mediaRating.setText(String.format("%01.01f", rating));
mediaMaxRating.setText(getString(R.string.max_rating_video));
} else {
mediaRating.setVisibility(View.INVISIBLE);
mediaMaxRating.setVisibility(View.INVISIBLE);
}
}
/**
* Display the cast details
*
* @param cursor Cursor with the data
*/
private void displayCastList(Cursor cursor) {
// Transform the cursor into a List<VideoType.Cast>
if (cursor.moveToFirst()) {
castArrayList = new ArrayList<VideoType.Cast>(cursor.getCount());
do {
castArrayList.add(new VideoType.Cast(cursor.getString(TVShowCastListQuery.NAME),
cursor.getInt(TVShowCastListQuery.ORDER),
cursor.getString(TVShowCastListQuery.ROLE),
cursor.getString(TVShowCastListQuery.THUMBNAIL)));
} while (cursor.moveToNext());
UIUtils.setupCastInfo(getActivity(), castArrayList, videoCastList,
AllCastActivity.buildLaunchIntent(getActivity(), tvshowTitle, castArrayList));
}
}
/**
* Checks wether we should refresh the TV Show details with the info on XBMC
* The details will be updated if the last update is older than what is configured in the
* settings
*
* @param cursor Cursor with the data
*/
private void checkOutdatedTVShowDetails(Cursor cursor) {
if (hasIssuedOutdatedRefresh)
return;
cursor.moveToFirst();
long lastUpdated = cursor.getLong(TVShowDetailsQuery.UPDATED);
if (System.currentTimeMillis() > lastUpdated + Settings.DB_UPDATE_INTERVAL) {
// Trigger a silent refresh
hasIssuedOutdatedRefresh = true;
startSync(true);
}
}
/**
* TV Show details query parameters.
*/
private interface TVShowDetailsQuery {
String[] PROJECTION = {
BaseColumns._ID,
MediaContract.TVShows.TITLE,
MediaContract.TVShows.THUMBNAIL,
MediaContract.TVShows.FANART,
MediaContract.TVShows.PREMIERED,
MediaContract.TVShows.STUDIO,
MediaContract.TVShows.EPISODE,
MediaContract.TVShows.WATCHEDEPISODES,
MediaContract.TVShows.RATING,
MediaContract.TVShows.PLOT,
MediaContract.TVShows.PLAYCOUNT,
MediaContract.TVShows.IMDBNUMBER,
MediaContract.TVShows.GENRES,
MediaContract.SyncColumns.UPDATED,
};
final int ID = 0;
final int TITLE = 1;
final int THUMBNAIL = 2;
final int FANART = 3;
final int PREMIERED = 4;
final int STUDIO = 5;
final int EPISODE = 6;
final int WATCHEDEPISODES = 7;
final int RATING = 8;
final int PLOT = 9;
final int PLAYCOUNT = 10;
final int IMDBNUMBER = 11;
final int GENRES = 12;
final int UPDATED = 13;
}
/**
* Movie cast list query parameters.
*/
public interface TVShowCastListQuery {
String[] PROJECTION = {
BaseColumns._ID,
MediaContract.TVShowCast.NAME,
MediaContract.TVShowCast.ORDER,
MediaContract.TVShowCast.ROLE,
MediaContract.TVShowCast.THUMBNAIL,
};
String SORT = MediaContract.TVShowCast.ORDER + " ASC";
final int ID = 0;
final int NAME = 1;
final int ORDER = 2;
final int ROLE = 3;
final int THUMBNAIL = 4;
}
}

View File

@ -42,18 +42,22 @@ import java.util.Map;
*/
public class TVShowsActivity extends BaseActivity
implements TVShowListFragment.OnTVShowSelectedListener,
TVShowEpisodeListFragment.OnEpisodeSelectedListener {
TVShowDetailsFragment.OnSeasonSelectedListener,
TVShowEpisodeListFragment.OnEpisodeSelectedListener {
private static final String TAG = LogUtils.makeLogTag(TVShowsActivity.class);
public static final String TVSHOWID = "tvshow_id";
public static final String TVSHOWTITLE = "tvshow_title";
public static final String EPISODEID = "episode_id";
public static final String SEASON = "season";
public static final String SEASONTITLE = "season_title";
private int selectedTVShowId = -1;
private String selectedTVShowTitle = null;
private int selectedSeason = -1;
private String selectedSeasonTitle = null;
private int selectedEpisodeId = -1;
private TVShowDetailsFragment tvshowDetailsFragment;
private boolean clearSharedElements;
private NavigationDrawerFragment navigationDrawerFragment;
@ -108,6 +112,8 @@ public class TVShowsActivity extends BaseActivity
selectedTVShowId = savedInstanceState.getInt(TVSHOWID, -1);
selectedTVShowTitle = savedInstanceState.getString(TVSHOWTITLE, null);
selectedEpisodeId = savedInstanceState.getInt(EPISODEID, -1);
selectedSeason = savedInstanceState.getInt(SEASON, -1);
selectedSeasonTitle = savedInstanceState.getString(SEASONTITLE, null);
}
setupActionBar(selectedTVShowTitle);
@ -124,6 +130,8 @@ public class TVShowsActivity extends BaseActivity
outState.putInt(TVSHOWID, selectedTVShowId);
outState.putString(TVSHOWTITLE, selectedTVShowTitle);
outState.putInt(EPISODEID, selectedEpisodeId);
outState.putInt(SEASON, selectedSeason);
outState.putString(SEASONTITLE, selectedSeasonTitle);
}
@Override
@ -152,6 +160,12 @@ public class TVShowsActivity extends BaseActivity
if (selectedEpisodeId != -1) {
selectedEpisodeId = -1;
getSupportFragmentManager().popBackStack();
setupActionBar(selectedSeasonTitle);
return true;
} else if (selectedSeason != -1) {
selectedSeason = -1;
getSupportFragmentManager().popBackStack();
setupActionBar(selectedTVShowTitle);
return true;
} else if (selectedTVShowId != -1) {
selectedTVShowId = -1;
@ -173,6 +187,10 @@ public class TVShowsActivity extends BaseActivity
// If we are showing episode or show details in portrait, clear selected and show action bar
if (selectedEpisodeId != -1) {
selectedEpisodeId = -1;
setupActionBar(selectedSeasonTitle);
} else if (selectedSeason != -1) {
selectedSeason = -1;
setupActionBar(selectedTVShowTitle);
} else if (selectedTVShowId != -1) {
selectedTVShowId = -1;
selectedTVShowTitle = null;
@ -182,19 +200,19 @@ public class TVShowsActivity extends BaseActivity
}
private boolean drawerIndicatorIsArrow = false;
private void setupActionBar(String tvshowTitle) {
private void setupActionBar(String title) {
Toolbar toolbar = (Toolbar)findViewById(R.id.default_toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar == null) return;
actionBar.setDisplayHomeAsUpEnabled(true);
if (tvshowTitle != null) {
if (title != null) {
if (!drawerIndicatorIsArrow) {
navigationDrawerFragment.animateDrawerToggle(true);
drawerIndicatorIsArrow = true;
}
actionBar.setTitle(tvshowTitle);
actionBar.setTitle(title);
} else {
if (drawerIndicatorIsArrow) {
navigationDrawerFragment.animateDrawerToggle(false);
@ -207,7 +225,7 @@ public class TVShowsActivity extends BaseActivity
/**
* Callback from tvshows list fragment when a show is selected.
* Switch fragment in portrait
* @param vh
* @param vh view holder
*/
@TargetApi(21)
public void onTVShowSelected(TVShowListFragment.ViewHolder vh) {
@ -215,7 +233,7 @@ public class TVShowsActivity extends BaseActivity
selectedTVShowTitle = vh.tvshowTitle;
// Replace list fragment
tvshowDetailsFragment = TVShowDetailsFragment.newInstance(vh);
final TVShowDetailsFragment tvshowDetailsFragment = TVShowDetailsFragment.newInstance(vh);
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
// Set up transitions
@ -238,55 +256,71 @@ public class TVShowsActivity extends BaseActivity
};
tvshowDetailsFragment.setEnterSharedElementCallback(seCallback);
tvshowDetailsFragment.setEnterTransition(TransitionInflater
.from(this)
.inflateTransition(R.transition.media_details));
tvshowDetailsFragment.setEnterTransition(
TransitionInflater.from(this)
.inflateTransition(R.transition.media_details));
tvshowDetailsFragment.setReturnTransition(null);
Transition changeImageTransition = TransitionInflater.from(
this).inflateTransition(R.transition.change_image);
Transition changeImageTransition =
TransitionInflater.from(this)
.inflateTransition(R.transition.change_image);
tvshowDetailsFragment.setSharedElementReturnTransition(changeImageTransition);
tvshowDetailsFragment.setSharedElementEnterTransition(changeImageTransition);
fragTrans.addSharedElement(vh.artView, vh.artView.getTransitionName());
} else {
fragTrans.setCustomAnimations(R.anim.fragment_details_enter, 0,
R.anim.fragment_list_popenter, 0);
fragTrans.setCustomAnimations(R.anim.fragment_details_enter, 0, R.anim.fragment_list_popenter, 0);
}
fragTrans.replace(R.id.fragment_container, tvshowDetailsFragment)
.addToBackStack(null)
.commit();
.addToBackStack(null)
.commit();
setupActionBar(selectedTVShowTitle);
}
/**
* Callback from tvshow episodes list when a episode is selected
* @param tvshowId Show id of the episode, should be the same as {@link #selectedTVShowId}
* @param episodeId Episode id
* Callback from tvshow details when a season is selected
* @param tvshowId tv show id
* @param seasonId season number
*/
@TargetApi(21)
public void onEpisodeSelected(int tvshowId, int episodeId) {
selectedEpisodeId = episodeId;
public void onSeasonSelected(int tvshowId, int seasonId) {
selectedSeason = seasonId;
// Replace fragment
TVShowEpisodeListFragment fragment =
TVShowEpisodeListFragment.newInstance(selectedTVShowId, seasonId);
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(R.anim.fragment_details_enter, 0, R.anim.fragment_list_popenter, 0)
.replace(R.id.fragment_container, fragment)
.addToBackStack(null)
.commit();
selectedSeasonTitle = String.format(getString(R.string.season_number), seasonId);
setupActionBar(selectedSeasonTitle);
}
/**
* Callback from tvshow episodes list when a episode is selected
* @param vh view holder
*/
public void onEpisodeSelected(TVShowEpisodeListFragment.EpisodeViewHolder vh) {
selectedEpisodeId = vh.episodeId;
// Replace list fragment
TVShowEpisodeDetailsFragment fragment =
TVShowEpisodeDetailsFragment.newInstance(tvshowId, episodeId);
TVShowEpisodeDetailsFragment.newInstance(selectedTVShowId, selectedEpisodeId);
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
// Set up transitions
if (Utils.isLollipopOrLater()) {
fragment.setEnterTransition(TransitionInflater
.from(this)
.inflateTransition(R.transition.media_details));
fragment.setEnterTransition(
TransitionInflater.from(this).inflateTransition(R.transition.media_details));
fragment.setReturnTransition(null);
} else {
fragTrans.setCustomAnimations(R.anim.fragment_details_enter, 0,
R.anim.fragment_list_popenter, 0);
fragTrans.setCustomAnimations(R.anim.fragment_details_enter, 0, R.anim.fragment_list_popenter, 0);
}
fragTrans.replace(R.id.fragment_container, fragment)
.addToBackStack(null)
.commit();
.addToBackStack(null)
.commit();
setupActionBar(selectedTVShowTitle);
}
}

View File

@ -184,6 +184,7 @@
style="@style/TextAppearance.Media.Info"
android:paddingLeft="@dimen/default_padding"
android:paddingRight="@dimen/default_padding"
android:paddingBottom="0dp"
android:maxLines="@integer/description_max_lines"
android:ellipsize="@null"/>
<ImageView

View File

@ -84,26 +84,6 @@
android:contentDescription="@string/poster"
android:scaleType="centerCrop"/>
<!-- Buttons-->
<!--<LinearLayout-->
<!--android:id="@+id/media_actions_bar"-->
<!--android:layout_width="match_parent"-->
<!--android:layout_height="@dimen/buttonbar_height"-->
<!--android:layout_below="@id/media_undertitle"-->
<!--android:paddingLeft="@dimen/default_padding"-->
<!--android:paddingRight="@dimen/default_padding"-->
<!--android:orientation="horizontal"-->
<!--style="@style/ButtonBar"-->
<!--android:background="?attr/contentBackgroundDimColor">-->
<!--<ImageButton-->
<!--android:id="@+id/go_to_imdb"-->
<!--android:layout_width="@dimen/buttonbar_button_width"-->
<!--android:layout_height="match_parent"-->
<!--style="@style/Widget.Button.Borderless"-->
<!--android:src="?attr/iconWWW"-->
<!--android:contentDescription="@string/imdb"/>-->
<!--</LinearLayout>-->
<RelativeLayout
android:id="@+id/media_details"
android:layout_width="match_parent"
@ -156,37 +136,81 @@
android:text="@string/max_rating_video"/>
</RelativeLayout>
<TextView
android:id="@+id/media_description"
<LinearLayout
android:id="@+id/media_description_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/media_details"
style="@style/TextAppearance.Media.Info"
android:paddingLeft="@dimen/default_padding"
android:paddingRight="@dimen/default_padding"
android:background="?attr/contentBackgroundColor"/>
android:orientation="vertical"
android:background="?attr/contentBackgroundColor">
<TextView
android:id="@+id/media_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.Media.Info"
android:paddingLeft="@dimen/default_padding"
android:paddingRight="@dimen/default_padding"
android:paddingBottom="0dp"
android:maxLines="@integer/description_max_lines"
android:ellipsize="@null"/>
<ImageView
android:id="@+id/show_all"
android:layout_width="@dimen/small_icon_size"
android:layout_height="@dimen/small_icon_size"
android:layout_gravity="end"
android:layout_marginRight="@dimen/small_padding"
android:layout_marginEnd="@dimen/small_padding"
android:src="?attr/iconExpand"
android:background="?android:attr/selectableItemBackground"/>
</LinearLayout>
<View
android:id="@+id/separator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/media_description"
android:layout_below="@id/media_description_container"
style="@style/DefaultDividerH"/>
<TextView
android:id="@+id/cast_title"
android:id="@+id/seasons_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/separator"
style="@style/TextAppearance.Media.Title"
android:paddingLeft="@dimen/default_padding"
android:paddingRight="@dimen/default_padding"
android:paddingTop="@dimen/default_padding"
android:text="@string/tvshow_seasons"/>
<!--
TODO: Review the column count.
Currently set to 1 in all cases as any other value isn't working
-->
<GridLayout
android:id="@+id/seasons_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/seasons_title"
android:columnCount="@integer/seasons_grid_view_columns"
android:orientation="horizontal"
android:useDefaultMargins="true"/>
<TextView
android:id="@+id/cast_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/seasons_list"
style="@style/TextAppearance.Media.Title"
android:paddingLeft="@dimen/default_padding"
android:paddingRight="@dimen/default_padding"
android:paddingTop="@dimen/default_padding"
android:text="@string/cast"/>
<GridLayout
android:id="@+id/cast_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/cast_title"
android:columnCount="@integer/cast_grid_view_columns"
android:orientation="horizontal"/>

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2015 Synced Synapse. All rights reserved.
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.
-->
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
card_view:cardElevation="@dimen/default_card_elevation"
card_view:cardBackgroundColor="?attr/appCardBackgroundColor"
android:focusable="true"
android:clickable="true"
android:foreground="?android:attr/selectableItemBackground">
<RelativeLayout
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/art"
android:layout_width="@dimen/seasonlist_art_width"
android:layout_height="@dimen/seasonlist_art_heigth"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:contentDescription="@string/poster"
android:scaleType="centerCrop"/>
<TextView
android:id="@+id/season"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/art"
android:layout_toEndOf="@id/art"
style="@style/TextAppearance.Medialist.Title"
android:paddingTop="@dimen/small_padding"/>
<TextView
android:id="@+id/episodes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/season"
android:layout_alignStart="@id/season"
android:layout_below="@id/season"
style="@style/TextAppearance.Medialist.Details"/>
</RelativeLayout>
</android.support.v7.widget.CardView>

View File

@ -15,69 +15,89 @@
limitations under the License.
-->
<RelativeLayout
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="@dimen/small_padding">
<ImageView
android:id="@+id/list_context_menu"
android:layout_width="@dimen/default_icon_size"
android:layout_height="@dimen/default_icon_size"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:padding="@dimen/default_icon_padding"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconOverflow"
android:contentDescription="@string/action_options"/>
card_view:cardElevation="@dimen/default_card_elevation"
card_view:cardBackgroundColor="?attr/appCardBackgroundColor">
<RelativeLayout
android:id="@+id/relativelayout_checkmark_epnumber"
android:layout_width="@dimen/seasonlist_art_width"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true">
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/art"
android:layout_width="@dimen/episodelist_art_width"
android:layout_height="@dimen/episodelist_art_heigth"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:contentDescription="@string/poster"
android:scaleType="centerCrop"/>
<TextView
android:id="@+id/episode_number"
android:layout_width="@dimen/default_icon_size"
android:layout_height="wrap_content"
style="@style/TextAppearance.Medialist.Title"
android:paddingRight="@dimen/small_padding"
android:layout_alignTop="@id/art"
android:layout_toRightOf="@id/art"
android:layout_toEndOf="@id/art"/>
<ImageView
android:id="@+id/checkmark"
android:layout_width="@dimen/default_icon_size"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="@dimen/small_padding"
android:contentDescription="@string/seen"
android:src="?attr/iconSeen"/>
android:layout_height="@dimen/default_icon_size"
android:layout_below="@id/episode_number"
android:layout_alignLeft="@id/episode_number"
android:layout_alignStart="@id/episode_number"
android:padding="@dimen/default_icon_padding"
android:src="?attr/iconSeen"
android:contentDescription="@string/seen"/>
<ImageView
android:id="@+id/list_context_menu"
android:layout_width="@dimen/default_icon_size"
android:layout_height="@dimen/default_icon_size"
android:layout_alignTop="@id/art"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:padding="@dimen/default_icon_padding"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconOverflow"
android:contentDescription="@string/action_options"/>
<TextView
android:id="@+id/episode_number"
android:layout_width="@dimen/seasonlist_art_width"
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/episode_number"
android:layout_toRightOf="@id/episode_number"
android:layout_toLeftOf="@id/list_context_menu"
android:layout_toEndOf="@id/episode_number"
android:layout_toStartOf="@id/list_context_menu"
style="@style/TextAppearance.Medialist.Title"
android:gravity="end"/>
android:paddingTop="@dimen/small_padding"
android:paddingLeft="@dimen/small_padding"
android:paddingRight="@dimen/small_padding"/>
<TextView
android:id="@+id/details"
android:layout_width="wrap_content"
android:layout_height="@dimen/default_icon_size"
android:layout_alignLeft="@id/title"
android:layout_alignStart="@id/title"
android:layout_below="@id/title"
style="@style/TextAppearance.Medialist.Details"
android:paddingLeft="@dimen/small_padding"
android:paddingTop="0dp"
android:paddingBottom="0dp"
android:gravity="center_vertical"/>
</RelativeLayout>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/relativelayout_checkmark_epnumber"
android:layout_toRightOf="@id/relativelayout_checkmark_epnumber"
android:layout_toLeftOf="@id/list_context_menu"
android:layout_toEndOf="@id/relativelayout_checkmark_epnumber"
android:layout_toStartOf="@id/list_context_menu"
style="@style/TextAppearance.Medialist.Title"
android:paddingTop="@dimen/small_padding"/>
<TextView
android:id="@+id/details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/title"
android:layout_alignStart="@id/title"
android:layout_below="@id/title"
style="@style/TextAppearance.Medialist.Details"
android:paddingBottom="@dimen/small_padding"/>
</RelativeLayout>
</android.support.v7.widget.CardView>

View File

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2015 Synced Synapse. All rights reserved.
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.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/art"
android:layout_width="@dimen/seasonlist_art_width"
android:layout_height="@dimen/seasonlist_art_heigth"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:contentDescription="@string/poster"
android:scaleType="centerCrop"/>
<TextView
android:id="@+id/season"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/art"
android:layout_toEndOf="@id/art"
style="@style/TextAppearance.Medialist.Title"
android:paddingTop="@dimen/small_padding"/>
<TextView
android:id="@+id/episodes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/season"
android:layout_alignStart="@id/season"
android:layout_below="@id/season"
style="@style/TextAppearance.Medialist.Details"/>
<ImageView
android:id="@+id/status_indicator"
android:layout_width="@dimen/default_icon_size"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="@dimen/small_padding"/>
</RelativeLayout>

View File

@ -17,4 +17,6 @@
<resources>
<integer name="cast_grid_view_columns">5</integer>
<integer name="cast_grid_view_rows">2</integer>
<integer name="seasons_grid_view_columns">1</integer>
</resources>

View File

@ -17,4 +17,6 @@
<resources>
<integer name="cast_grid_view_columns">6</integer>
<integer name="cast_grid_view_rows">2</integer>
<integer name="seasons_grid_view_columns">1</integer>
</resources>

View File

@ -45,8 +45,8 @@
<dimen name="seasonlist_art_width">86dp</dimen>
<dimen name="seasonlist_art_heigth">124dp</dimen>
<dimen name="episodelist_art_width">134dp</dimen>
<dimen name="episodelist_art_heigth">76dp</dimen>
<dimen name="episodelist_art_width">112dp</dimen>
<dimen name="episodelist_art_heigth">100dp</dimen>
<dimen name="artistlist_art_width">88dp</dimen>
<dimen name="artistlist_art_heigth">88dp</dimen>

View File

@ -16,5 +16,7 @@
-->
<resources>
<integer name="cast_grid_view_columns">4</integer>
<integer name="cast_grid_view_rows">3</integer>
<integer name="cast_grid_view_rows">3</integer>
<integer name="seasons_grid_view_columns">1</integer>
</resources>

View File

@ -86,8 +86,8 @@
<dimen name="seasonlist_art_width">72dp</dimen>
<dimen name="seasonlist_art_heigth">104dp</dimen>
<dimen name="episodelist_art_width">112dp</dimen>
<dimen name="episodelist_art_heigth">64dp</dimen>
<dimen name="episodelist_art_width">84dp</dimen>
<dimen name="episodelist_art_heigth">84dp</dimen>
<dimen name="artistlist_art_width">74dp</dimen>
<dimen name="artistlist_art_heigth">74dp</dimen>

View File

@ -25,5 +25,7 @@
<integer name="cast_grid_view_rows">3</integer>
<integer name="description_max_lines">3</integer>
<integer name="seasons_grid_view_columns">1</integer>
<integer name="fab_animation_start_delay">100</integer>
</resources>

View File

@ -287,6 +287,7 @@
<string name="tvshow_overview">Overview</string>
<string name="tvshow_episodes">Episodes</string>
<string name="tvshow_seasons">Seasons</string>
<string name="addon_overview">Overview</string>
<string name="addon_content">Content</string>