diff --git a/app/src/main/java/org/xbmc/kore/ui/AbstractCursorListFragment.java b/app/src/main/java/org/xbmc/kore/ui/AbstractCursorListFragment.java index 6349359..da6821b 100644 --- a/app/src/main/java/org/xbmc/kore/ui/AbstractCursorListFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/AbstractCursorListFragment.java @@ -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); } diff --git a/app/src/main/java/org/xbmc/kore/ui/MoviesActivity.java b/app/src/main/java/org/xbmc/kore/ui/MoviesActivity.java index 4216640..9df0d78 100644 --- a/app/src/main/java/org/xbmc/kore/ui/MoviesActivity.java +++ b/app/src/main/java/org/xbmc/kore/ui/MoviesActivity.java @@ -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 names, Map 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); } diff --git a/app/src/main/java/org/xbmc/kore/ui/TVShowDetailsFragment.java b/app/src/main/java/org/xbmc/kore/ui/TVShowDetailsFragment.java index 289d4fa..ef29fc7 100644 --- a/app/src/main/java/org/xbmc/kore/ui/TVShowDetailsFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/TVShowDetailsFragment.java @@ -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 { 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 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 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 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 + if (cursor.moveToFirst()) { + ArrayList castArrayList = new ArrayList(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; + } } diff --git a/app/src/main/java/org/xbmc/kore/ui/TVShowEpisodeListFragment.java b/app/src/main/java/org/xbmc/kore/ui/TVShowEpisodeListFragment.java index 075edcf..096ec70 100644 --- a/app/src/main/java/org/xbmc/kore/ui/TVShowEpisodeListFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/TVShowEpisodeListFragment.java @@ -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 { +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 groupsExpanded = new HashMap<>(); - private HashMap 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 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 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 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() { diff --git a/app/src/main/java/org/xbmc/kore/ui/TVShowOverviewFragment.java b/app/src/main/java/org/xbmc/kore/ui/TVShowOverviewFragment.java deleted file mode 100644 index 0a2465e..0000000 --- a/app/src/main/java/org/xbmc/kore/ui/TVShowOverviewFragment.java +++ /dev/null @@ -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 { - 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 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 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 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 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 - - if (cursor.moveToFirst()) { - castArrayList = new ArrayList(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; - } -} diff --git a/app/src/main/java/org/xbmc/kore/ui/TVShowsActivity.java b/app/src/main/java/org/xbmc/kore/ui/TVShowsActivity.java index e444b98..d6b4992 100644 --- a/app/src/main/java/org/xbmc/kore/ui/TVShowsActivity.java +++ b/app/src/main/java/org/xbmc/kore/ui/TVShowsActivity.java @@ -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); } - } diff --git a/app/src/main/res/layout/fragment_album_details.xml b/app/src/main/res/layout/fragment_album_details.xml index f35fa61..be4f5b0 100644 --- a/app/src/main/res/layout/fragment_album_details.xml +++ b/app/src/main/res/layout/fragment_album_details.xml @@ -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"/> - - - - - - - - - - - - - - - - - - - - - + android:orientation="vertical" + android:background="?attr/contentBackgroundColor"> + + + + + + + + + diff --git a/app/src/main/res/layout/grid_item_season.xml b/app/src/main/res/layout/grid_item_season.xml new file mode 100644 index 0000000..9b3e135 --- /dev/null +++ b/app/src/main/res/layout/grid_item_season.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_episode.xml b/app/src/main/res/layout/list_item_episode.xml index 22ae303..b252d2a 100644 --- a/app/src/main/res/layout/list_item_episode.xml +++ b/app/src/main/res/layout/list_item_episode.xml @@ -15,69 +15,89 @@ limitations under the License. --> - - - + card_view:cardElevation="@dimen/default_card_elevation" + card_view:cardBackgroundColor="?attr/appCardBackgroundColor"> + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + + + + 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"/> + + + android:paddingTop="@dimen/small_padding" + android:paddingLeft="@dimen/small_padding" + android:paddingRight="@dimen/small_padding"/> + + + + - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_season.xml b/app/src/main/res/layout/list_item_season.xml deleted file mode 100644 index 2b13e96..0000000 --- a/app/src/main/res/layout/list_item_season.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/values-land/integers.xml b/app/src/main/res/values-land/integers.xml index b97014d..5fcbebc 100644 --- a/app/src/main/res/values-land/integers.xml +++ b/app/src/main/res/values-land/integers.xml @@ -17,4 +17,6 @@ 5 2 + + 1 \ No newline at end of file diff --git a/app/src/main/res/values-sw600dp-land/integers.xml b/app/src/main/res/values-sw600dp-land/integers.xml index f54a629..0a1e228 100644 --- a/app/src/main/res/values-sw600dp-land/integers.xml +++ b/app/src/main/res/values-sw600dp-land/integers.xml @@ -17,4 +17,6 @@ 6 2 + + 1 \ No newline at end of file diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml index 5923042..4b02d73 100644 --- a/app/src/main/res/values-sw600dp/dimens.xml +++ b/app/src/main/res/values-sw600dp/dimens.xml @@ -45,8 +45,8 @@ 86dp 124dp - 134dp - 76dp + 112dp + 100dp 88dp 88dp diff --git a/app/src/main/res/values-sw600dp/integers.xml b/app/src/main/res/values-sw600dp/integers.xml index 1af4ea1..2501635 100644 --- a/app/src/main/res/values-sw600dp/integers.xml +++ b/app/src/main/res/values-sw600dp/integers.xml @@ -16,5 +16,7 @@ --> 4 - 3 + 3 + + 1 \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 0b4a894..18df0b1 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -86,8 +86,8 @@ 72dp 104dp - 112dp - 64dp + 84dp + 84dp 74dp 74dp diff --git a/app/src/main/res/values/integers.xml b/app/src/main/res/values/integers.xml index 8be3f17..744ce14 100644 --- a/app/src/main/res/values/integers.xml +++ b/app/src/main/res/values/integers.xml @@ -25,5 +25,7 @@ 3 3 + 1 + 100 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e538080..0406378 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -287,6 +287,7 @@ Overview Episodes + Seasons Overview Content