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 155f28f..289d4fa 100644 --- a/app/src/main/java/org/xbmc/kore/ui/TVShowDetailsFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/TVShowDetailsFragment.java @@ -15,6 +15,7 @@ */ package org.xbmc.kore.ui; +import android.annotation.TargetApi; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.view.ViewPager; @@ -23,9 +24,12 @@ import android.view.View; import android.view.ViewGroup; import com.astuetz.PagerSlidingTabStrip; + import org.xbmc.kore.R; 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 butterknife.ButterKnife; import butterknife.InjectView; @@ -36,22 +40,45 @@ import butterknife.InjectView; public class TVShowDetailsFragment extends Fragment { private static final String TAG = LogUtils.makeLogTag(TVShowDetailsFragment.class); - public static final String TVSHOWID = "tvshow_id"; + public static final String POSTER_TRANS_NAME = "POSTER_TRANS_NAME"; + public static final String BUNDLE_KEY_TVSHOWID = "tvshow_id"; + public static final String BUNDLE_KEY_TITLE = "title"; + public static final String BUNDLE_KEY_PREMIERED = "premiered"; + public static final String BUNDLE_KEY_STUDIO = "studio"; + public static final String BUNDLE_KEY_EPISODE = "episode"; + public static final String BUNDLE_KEY_WATCHEDEPISODES = "watchedepisodes"; + public static final String BUNDLE_KEY_RATING = "rating"; + public static final String BUNDLE_KEY_PLOT = "plot"; + public static final String BUNDLE_KEY_GENRES = "genres"; // Displayed movie id private int tvshowId = -1; + private TabsAdapter tabsAdapter; + @InjectView(R.id.pager_tab_strip) PagerSlidingTabStrip pagerTabStrip; @InjectView(R.id.pager) ViewPager viewPager; /** * Create a new instance of this, initialized to show tvshowId */ - public static TVShowDetailsFragment newInstance(int tvshowId) { + @TargetApi(21) + public static TVShowDetailsFragment newInstance(TVShowListFragment.ViewHolder vh) { TVShowDetailsFragment fragment = new TVShowDetailsFragment(); Bundle args = new Bundle(); - args.putInt(TVSHOWID, tvshowId); + args.putInt(BUNDLE_KEY_TVSHOWID, vh.tvshowId); + args.putInt(BUNDLE_KEY_EPISODE, vh.episode); + args.putString(BUNDLE_KEY_GENRES, vh.genres); + args.putString(BUNDLE_KEY_PLOT, vh.plot); + args.putString(BUNDLE_KEY_PREMIERED, vh.premiered); + args.putDouble(BUNDLE_KEY_RATING, vh.rating); + args.putString(BUNDLE_KEY_STUDIO, vh.studio); + args.putString(BUNDLE_KEY_TITLE, vh.tvshowTitle); + args.putInt(BUNDLE_KEY_WATCHEDEPISODES, vh.watchedEpisodes); + if( Utils.isLollipopOrLater()) { + args.putString(POSTER_TRANS_NAME, vh.artView.getTransitionName()); + } fragment.setArguments(args); return fragment; } @@ -63,7 +90,7 @@ public class TVShowDetailsFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - tvshowId = getArguments().getInt(TVSHOWID, -1); + tvshowId = getArguments().getInt(BUNDLE_KEY_TVSHOWID, -1); if ((container == null) || (tvshowId == -1)) { // We're not being shown or there's nothing to show @@ -74,7 +101,7 @@ public class TVShowDetailsFragment extends Fragment { ButterKnife.inject(this, root); long baseFragmentId = tvshowId * 10; - TabsAdapter tabsAdapter = new TabsAdapter(getActivity(), getChildFragmentManager()) + tabsAdapter = new TabsAdapter(getActivity(), getChildFragmentManager()) .addTab(TVShowOverviewFragment.class, getArguments(), R.string.tvshow_overview, baseFragmentId) .addTab(TVShowEpisodeListFragment.class, getArguments(), @@ -92,19 +119,26 @@ public class TVShowDetailsFragment extends Fragment { setHasOptionsMenu(false); } - @Override - public void onResume() { - super.onResume(); + public Fragment getCurrentTabFragment() { + return tabsAdapter.getItem(viewPager.getCurrentItem()); } - @Override - public void onPause() { - super.onPause(); - } + public View getSharedElement() { + View view = getView(); + if (view == null) + return null; - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); -// outState.putInt(TVSHOWID, tvshowId); + //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; + } + + return null; } } diff --git a/app/src/main/java/org/xbmc/kore/ui/TVShowListFragment.java b/app/src/main/java/org/xbmc/kore/ui/TVShowListFragment.java index 6aa9220..c4f7376 100644 --- a/app/src/main/java/org/xbmc/kore/ui/TVShowListFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/TVShowListFragment.java @@ -15,6 +15,7 @@ */ package org.xbmc.kore.ui; +import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; @@ -47,6 +48,7 @@ import org.xbmc.kore.provider.MediaDatabase; import org.xbmc.kore.service.LibrarySyncService; import org.xbmc.kore.utils.LogUtils; import org.xbmc.kore.utils.UIUtils; +import org.xbmc.kore.utils.Utils; /** * Fragment that presents the tv show list @@ -55,7 +57,7 @@ public class TVShowListFragment extends AbstractListFragment { private static final String TAG = LogUtils.makeLogTag(TVShowListFragment.class); public interface OnTVShowSelectedListener { - public void onTVShowSelected(int tvshowId, String tvshowTitle); + public void onTVShowSelected(TVShowListFragment.ViewHolder vh); } // Activity listener @@ -72,7 +74,7 @@ public class TVShowListFragment extends AbstractListFragment { // Get the movie id from the tag ViewHolder tag = (ViewHolder) view.getTag(); // Notify the activity - listenerActivity.onTVShowSelected(tag.tvshowId, tag.tvshowTitle); + listenerActivity.onTVShowSelected(tag); } }; } @@ -225,9 +227,16 @@ public class TVShowListFragment extends AbstractListFragment { MediaContract.TVShows.TVSHOWID, 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, }; String SORT_BY_NAME = MediaContract.TVShows.TITLE + " ASC"; @@ -238,9 +247,16 @@ public class TVShowListFragment extends AbstractListFragment { final int TVSHOWID = 1; final int TITLE = 2; final int THUMBNAIL = 3; - final int PREMIERED = 4; - final int EPISODE = 5; - final int WATCHEDEPISODES = 6; + final int FANART = 4; + final int PREMIERED = 5; + final int STUDIO = 6; + final int EPISODE = 7; + final int WATCHEDEPISODES = 8; + final int RATING = 9; + final int PLOT = 10; + final int PLAYCOUNT = 11; + final int IMDBNUMBER = 12; + final int GENRES = 13; } private static class TVShowsAdapter extends CursorAdapter { @@ -257,16 +273,16 @@ public class TVShowListFragment extends AbstractListFragment { // the user transitions to that fragment, avoiding another call and imediatelly showing the image Resources resources = context.getResources(); artWidth = (int)(resources.getDimension(R.dimen.now_playing_poster_width) / - UIUtils.IMAGE_RESIZE_FACTOR); + UIUtils.IMAGE_RESIZE_FACTOR); artHeight = (int)(resources.getDimension(R.dimen.now_playing_poster_height) / - UIUtils.IMAGE_RESIZE_FACTOR); + UIUtils.IMAGE_RESIZE_FACTOR); } /** {@inheritDoc} */ @Override public View newView(Context context, final Cursor cursor, ViewGroup parent) { final View view = LayoutInflater.from(context) - .inflate(R.layout.grid_item_tvshow, parent, false); + .inflate(R.layout.grid_item_tvshow, parent, false); // Setup View holder pattern ViewHolder viewHolder = new ViewHolder(); @@ -281,6 +297,7 @@ public class TVShowListFragment extends AbstractListFragment { } /** {@inheritDoc} */ + @TargetApi(21) @Override public void bindView(View view, Context context, Cursor cursor) { final ViewHolder viewHolder = (ViewHolder)view.getTag(); @@ -288,16 +305,25 @@ public class TVShowListFragment extends AbstractListFragment { // Save the movie id viewHolder.tvshowId = cursor.getInt(TVShowListQuery.TVSHOWID); viewHolder.tvshowTitle = cursor.getString(TVShowListQuery.TITLE); + viewHolder.episode = cursor.getInt(TVShowListQuery.EPISODE); + viewHolder.genres = cursor.getString(TVShowListQuery.GENRES); + viewHolder.plot = cursor.getString(TVShowListQuery.PLOT); + viewHolder.premiered = cursor.getString(TVShowListQuery.PREMIERED); + viewHolder.rating = cursor.getInt(TVShowListQuery.RATING); + viewHolder.studio = cursor.getString(TVShowListQuery.STUDIO); + viewHolder.watchedEpisodes = cursor.getInt(TVShowListQuery.WATCHEDEPISODES); + + if(Utils.isLollipopOrLater()) { + viewHolder.artView.setTransitionName("a"+viewHolder.tvshowId); + } viewHolder.titleView.setText(viewHolder.tvshowTitle); - int numEpisodes = cursor.getInt(TVShowListQuery.EPISODE), - watchedEpisodes = cursor.getInt(TVShowListQuery.WATCHEDEPISODES); String details = String.format(context.getString(R.string.num_episodes), - numEpisodes, numEpisodes - watchedEpisodes); + viewHolder.episode, viewHolder.episode - viewHolder.watchedEpisodes); viewHolder.detailsView.setText(details); String premiered = String.format(context.getString(R.string.premiered), - cursor.getString(TVShowListQuery.PREMIERED)); + viewHolder.premiered); viewHolder.premieredView.setText(premiered); UIUtils.loadImageWithCharacterAvatar(context, hostManager, cursor.getString(TVShowListQuery.THUMBNAIL), viewHolder.tvshowTitle, @@ -308,14 +334,21 @@ public class TVShowListFragment extends AbstractListFragment { /** * View holder pattern */ - private static class ViewHolder { + public static class ViewHolder { TextView titleView; TextView detailsView; -// TextView yearView; + // TextView yearView; TextView premieredView; ImageView artView; int tvshowId; String tvshowTitle; + String premiered; + String studio; + int episode; + int watchedEpisodes; + double rating; + String plot; + String genres; } } diff --git a/app/src/main/java/org/xbmc/kore/ui/TVShowOverviewFragment.java b/app/src/main/java/org/xbmc/kore/ui/TVShowOverviewFragment.java index 1176f12..60debd0 100644 --- a/app/src/main/java/org/xbmc/kore/ui/TVShowOverviewFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/TVShowOverviewFragment.java @@ -15,6 +15,7 @@ */ package org.xbmc.kore.ui; +import android.annotation.TargetApi; import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; @@ -42,6 +43,7 @@ import org.xbmc.kore.provider.MediaContract; import org.xbmc.kore.service.LibrarySyncService; import org.xbmc.kore.utils.LogUtils; import org.xbmc.kore.utils.UIUtils; +import org.xbmc.kore.utils.Utils; import java.util.ArrayList; @@ -105,8 +107,10 @@ public class TVShowOverviewFragment extends AbstractDetailsFragment } @Override + @TargetApi(21) protected View createView(LayoutInflater inflater, ViewGroup container) { - tvshowId = getArguments().getInt(TVSHOWID, -1); + Bundle bundle = getArguments(); + tvshowId = bundle.getInt(TVShowDetailsFragment.BUNDLE_KEY_TVSHOWID, -1); if (tvshowId == -1) { // There's nothing to show @@ -130,6 +134,18 @@ public class TVShowOverviewFragment extends AbstractDetailsFragment } }); + 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); @@ -244,26 +260,13 @@ public class TVShowOverviewFragment extends AbstractDetailsFragment mediaTitle.setText(tvshowTitle); int numEpisodes = cursor.getInt(TVShowDetailsQuery.EPISODE), watchedEpisodes = cursor.getInt(TVShowDetailsQuery.WATCHEDEPISODES); - String episodes = String.format(getString(R.string.num_episodes), - numEpisodes, numEpisodes - watchedEpisodes); - mediaUndertitle.setText(episodes); + setMediaUndertitle(numEpisodes, watchedEpisodes); + + setMediaPremiered(cursor.getString(TVShowDetailsQuery.PREMIERED), cursor.getString(TVShowDetailsQuery.STUDIO)); - String premiered = String.format(getString(R.string.premiered), - cursor.getString(TVShowDetailsQuery.PREMIERED)) + - " | " + cursor.getString(TVShowDetailsQuery.STUDIO); - mediaPremiered.setText(premiered); mediaGenres.setText(cursor.getString(TVShowDetailsQuery.GENRES)); - double rating = cursor.getDouble(TVShowDetailsQuery.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); - } + setMediaRating(cursor.getDouble(TVShowDetailsQuery.RATING)); mediaDescription.setText(cursor.getString(TVShowDetailsQuery.PLOT)); @@ -286,6 +289,28 @@ public class TVShowOverviewFragment extends AbstractDetailsFragment 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 * 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 c2f3ee1..e444b98 100644 --- a/app/src/main/java/org/xbmc/kore/ui/TVShowsActivity.java +++ b/app/src/main/java/org/xbmc/kore/ui/TVShowsActivity.java @@ -22,15 +22,20 @@ import android.support.v4.app.FragmentTransaction; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; +import android.transition.Transition; import android.transition.TransitionInflater; import android.view.Menu; import android.view.MenuItem; +import android.view.View; import android.view.Window; import org.xbmc.kore.R; import org.xbmc.kore.utils.LogUtils; import org.xbmc.kore.utils.Utils; +import java.util.List; +import java.util.Map; + /** * Controls the presentation of TV Shows information (list, details) * All the information is presented by specific fragments @@ -48,6 +53,9 @@ public class TVShowsActivity extends BaseActivity private String selectedTVShowTitle = null; private int selectedEpisodeId = -1; + private TVShowDetailsFragment tvshowDetailsFragment; + private boolean clearSharedElements; + private NavigationDrawerFragment navigationDrawerFragment; @TargetApi(21) @@ -70,10 +78,27 @@ public class TVShowsActivity extends BaseActivity // Setup animations if (Utils.isLollipopOrLater()) { - tvshowListFragment.setExitTransition(null); - tvshowListFragment.setReenterTransition(TransitionInflater + //Fade added to prevent shared element from disappearing very shortly at the start of the transition. + Transition fade = TransitionInflater .from(this) - .inflateTransition(android.R.transition.fade)); + .inflateTransition(android.R.transition.fade); + tvshowListFragment.setExitTransition(fade); + tvshowListFragment.setReenterTransition(fade); + + tvshowListFragment.setSharedElementReturnTransition(TransitionInflater.from( + this).inflateTransition(R.transition.change_image)); + + android.support.v4.app.SharedElementCallback seCallback = new android.support.v4.app.SharedElementCallback() { + @Override + public void onMapSharedElements(List names, Map sharedElements) { + if (clearSharedElements) { + names.clear(); + sharedElements.clear(); + clearSharedElements = false; + } + } + }; + tvshowListFragment.setExitSharedElementCallback(seCallback); } getSupportFragmentManager() .beginTransaction() @@ -93,16 +118,6 @@ public class TVShowsActivity extends BaseActivity // UIUtils.setPaddingForSystemBars(this, findViewById(R.id.drawer_layout), true, true, true); } - @Override - public void onResume() { - super.onResume(); - } - - @Override - public void onPause() { - super.onPause(); - } - @Override protected void onSaveInstanceState (Bundle outState) { super.onSaveInstanceState(outState); @@ -192,24 +207,46 @@ public class TVShowsActivity extends BaseActivity /** * Callback from tvshows list fragment when a show is selected. * Switch fragment in portrait - * @param tvshowId Selected show - * @param tvshowTitle Title + * @param vh */ @TargetApi(21) - public void onTVShowSelected(int tvshowId, String tvshowTitle) { - selectedTVShowId = tvshowId; - selectedTVShowTitle = tvshowTitle; + public void onTVShowSelected(TVShowListFragment.ViewHolder vh) { + selectedTVShowId = vh.tvshowId; + selectedTVShowTitle = vh.tvshowTitle; // Replace list fragment - TVShowDetailsFragment tvshowDetailsFragment = TVShowDetailsFragment.newInstance(tvshowId); + tvshowDetailsFragment = TVShowDetailsFragment.newInstance(vh); FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction(); // Set up transitions if (Utils.isLollipopOrLater()) { + android.support.v4.app.SharedElementCallback seCallback = new android.support.v4.app.SharedElementCallback() { + @Override + public void onMapSharedElements(List names, Map sharedElements) { + //On returning onMapSharedElements for the exiting fragment is called before the onMapSharedElements + // for the reentering fragment. We use this to determine if we are returning and if + // we should clear the shared element lists. Note that, clearing must be done in the reentering fragment + // as this is called last. Otherwise it the app will crash during transition setup. Not sure, but might + // be a v4 support package bug. + if (tvshowDetailsFragment.isVisible()) { + View sharedView = tvshowDetailsFragment.getSharedElement(); + if (sharedView == null) { // shared element not visible + clearSharedElements = true; + } + } + } + }; + tvshowDetailsFragment.setEnterSharedElementCallback(seCallback); + tvshowDetailsFragment.setEnterTransition(TransitionInflater .from(this) .inflateTransition(R.transition.media_details)); tvshowDetailsFragment.setReturnTransition(null); + 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); diff --git a/app/src/main/java/org/xbmc/kore/utils/UIUtils.java b/app/src/main/java/org/xbmc/kore/utils/UIUtils.java index cdc1df2..b28160a 100644 --- a/app/src/main/java/org/xbmc/kore/utils/UIUtils.java +++ b/app/src/main/java/org/xbmc/kore/utils/UIUtils.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Rect; import android.os.Vibrator; import android.preference.PreferenceManager; import android.support.annotation.NonNull; @@ -462,4 +463,13 @@ public class UIUtils { } }); } + + /** + * Returns true if {@param view} is contained within {@param container}'s bounds. + */ + public static boolean isViewInBounds(@NonNull View container, @NonNull View view) { + Rect containerBounds = new Rect(); + container.getHitRect(containerBounds); + return view.getLocalVisibleRect(containerBounds); + } } diff --git a/app/src/main/res/transition-v21/media_details.xml b/app/src/main/res/transition-v21/media_details.xml index f7d8877..a6cb737 100644 --- a/app/src/main/res/transition-v21/media_details.xml +++ b/app/src/main/res/transition-v21/media_details.xml @@ -18,6 +18,7 @@ +