diff --git a/app/src/main/java/org/xbmc/kore/ui/AddonDetailsFragment.java b/app/src/main/java/org/xbmc/kore/ui/AddonDetailsFragment.java index dbb7ea3..0496647 100644 --- a/app/src/main/java/org/xbmc/kore/ui/AddonDetailsFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/AddonDetailsFragment.java @@ -15,6 +15,7 @@ */ package org.xbmc.kore.ui; +import android.annotation.TargetApi; import android.content.res.Resources; import android.content.res.TypedArray; import android.os.Bundle; @@ -39,9 +40,9 @@ import org.xbmc.kore.host.HostInfo; import org.xbmc.kore.host.HostManager; import org.xbmc.kore.jsonrpc.ApiCallback; import org.xbmc.kore.jsonrpc.method.Addons; -import org.xbmc.kore.jsonrpc.type.AddonType; import org.xbmc.kore.utils.LogUtils; import org.xbmc.kore.utils.UIUtils; +import org.xbmc.kore.utils.Utils; import butterknife.ButterKnife; import butterknife.InjectView; @@ -53,7 +54,15 @@ import butterknife.OnClick; public class AddonDetailsFragment extends Fragment { private static final String TAG = LogUtils.makeLogTag(AddonDetailsFragment.class); - public static final String ADDONID = "addon_id"; + public static final String BUNDLE_KEY_ADDONID = "addon_id"; + public static final String POSTER_TRANS_NAME = "POSTER_TRANS_NAME"; + public static final String BUNDLE_KEY_NAME = "name"; + public static final String BUNDLE_KEY_AUTHOR = "author"; + public static final String BUNDLE_KEY_SUMMARY = "summary"; + public static final String BUNDLE_KEY_VERSION = "version"; + public static final String BUNDLE_KEY_DESCRIPTION = "description"; + public static final String BUNDLE_KEY_FANART = "fanart"; + public static final String BUNDLE_KEY_POSTER = "poster"; private HostManager hostManager; private HostInfo hostInfo; @@ -87,11 +96,23 @@ public class AddonDetailsFragment extends Fragment { /** * Create a new instance of this, initialized to show the addon addonId */ - public static AddonDetailsFragment newInstance(String addonId) { + @TargetApi(21) + public static AddonDetailsFragment newInstance(AddonListFragment.ViewHolder vh) { AddonDetailsFragment fragment = new AddonDetailsFragment(); Bundle args = new Bundle(); - args.putString(ADDONID, addonId); + args.putString(BUNDLE_KEY_ADDONID, vh.addonId); + args.putString(BUNDLE_KEY_NAME, vh.addonName); + args.putString(BUNDLE_KEY_AUTHOR, vh.author); + args.putString(BUNDLE_KEY_VERSION, vh.version); + args.putString(BUNDLE_KEY_SUMMARY, vh.summary); + args.putString(BUNDLE_KEY_DESCRIPTION, vh.description); + args.putString(BUNDLE_KEY_FANART, vh.fanart); + args.putString(BUNDLE_KEY_POSTER, vh.poster); + + if( Utils.isLollipopOrLater()) { + args.putString(POSTER_TRANS_NAME, vh.artView.getTransitionName()); + } fragment.setArguments(args); return fragment; } @@ -101,9 +122,11 @@ public class AddonDetailsFragment extends Fragment { super.onCreate(savedInstanceState); } + @TargetApi(21) @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - addonId = getArguments().getString(ADDONID, null); + Bundle bundle = getArguments(); + addonId = bundle.getString(BUNDLE_KEY_ADDONID, null); if ((container == null) || (addonId == null)) { // We're not being shown or there's nothing to show @@ -131,6 +154,18 @@ public class AddonDetailsFragment extends Fragment { FloatingActionButton fab = (FloatingActionButton)fabButton; fab.attachToScrollView((ObservableScrollView) mediaPanel); + if(Utils.isLollipopOrLater()) { + mediaPoster.setTransitionName(bundle.getString(POSTER_TRANS_NAME)); + } + + mediaTitle.setText(bundle.getString(BUNDLE_KEY_NAME)); + mediaUndertitle.setText(bundle.getString(BUNDLE_KEY_SUMMARY)); + mediaAuthor.setText(bundle.getString(BUNDLE_KEY_AUTHOR)); + mediaVersion.setText(bundle.getString(BUNDLE_KEY_VERSION)); + mediaDescription.setText(bundle.getString(BUNDLE_KEY_DESCRIPTION)); + + setImages(bundle.getString(BUNDLE_KEY_POSTER), bundle.getString(BUNDLE_KEY_FANART)); + // Pad main content view to overlap with bottom system bar // UIUtils.setPaddingForSystemBars(getActivity(), mediaPanel, false, false, true); // mediaPanel.setClipToPadding(false); @@ -142,31 +177,6 @@ public class AddonDetailsFragment extends Fragment { public void onActivityCreated (Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setHasOptionsMenu(false); - - // Get the addon details, this is done asyhnchronously - String[] properties = new String[] { - AddonType.Fields.NAME, AddonType.Fields.VERSION, AddonType.Fields.SUMMARY, - AddonType.Fields.DESCRIPTION, AddonType.Fields.PATH, AddonType.Fields.AUTHOR, - AddonType.Fields.THUMBNAIL, AddonType.Fields.DISCLAIMER, AddonType.Fields.FANART, - //AddonType.Fields.DEPENDENCIES, AddonType.Fields.BROKEN, AddonType.Fields.EXTRAINFO, - AddonType.Fields.RATING, AddonType.Fields.ENABLED - }; - Addons.GetAddonDetails action = new Addons.GetAddonDetails(addonId, properties); - action.execute(hostManager.getConnection(), new ApiCallback() { - @Override - public void onSuccess(AddonType.Details result) { - if (!isAdded()) return; - displayAddonDetails(result); - } - - @Override - public void onError(int errorCode, String description) { - if (!isAdded()) return; - Toast.makeText(getActivity(), - String.format(getString(R.string.error_getting_addon_info), description), - Toast.LENGTH_SHORT).show(); - } - }, callbackHandler); } @Override @@ -225,70 +235,39 @@ public class AddonDetailsFragment extends Fragment { public void onError(int errorCode, String description) { if (!isAdded()) return; Toast.makeText(getActivity(), - String.format(getString(R.string.general_error_executing_action), description), - Toast.LENGTH_SHORT) - .show(); + String.format(getString(R.string.general_error_executing_action), description), + Toast.LENGTH_SHORT) + .show(); } }, callbackHandler); } - /** - * Display the addon details - * - * @param addonDetails Addon details - */ - private void displayAddonDetails(AddonType.Details addonDetails) { - mediaTitle.setText(addonDetails.name); - mediaUndertitle.setText(addonDetails.summary); - - mediaAuthor.setText(addonDetails.author); - mediaVersion.setText(addonDetails.version); - - mediaDescription.setText(addonDetails.description); - - // Images + private void setImages(String poster, String fanart) { Resources resources = getActivity().getResources(); DisplayMetrics displayMetrics = new DisplayMetrics(); getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); - int artHeight = resources.getDimensionPixelOffset(R.dimen.now_playing_art_height), artWidth = displayMetrics.widthPixels; - if (!TextUtils.isEmpty(addonDetails.fanart)) { - int posterWidth = resources.getDimensionPixelOffset(R.dimen.addondetail_poster_width); - int posterHeight = resources.getDimensionPixelOffset(R.dimen.addondetail_poster_heigth); - mediaPoster.setVisibility(View.VISIBLE); - UIUtils.loadImageIntoImageview(hostManager, - addonDetails.thumbnail, - mediaPoster, posterWidth, posterHeight); - UIUtils.loadImageIntoImageview(hostManager, - addonDetails.fanart, - mediaArt, artWidth, artHeight); - } else { - // No fanart, just present the poster - mediaPoster.setVisibility(View.GONE); - UIUtils.loadImageIntoImageview(hostManager, - addonDetails.thumbnail, - mediaArt, artWidth, artHeight); - // Reset padding - int paddingLeft = mediaTitle.getPaddingRight(), - paddingRight = mediaTitle.getPaddingRight(), - paddingTop = mediaTitle.getPaddingTop(), - paddingBottom = mediaTitle.getPaddingBottom(); - mediaTitle.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); - mediaUndertitle.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); - } + int posterWidth = resources.getDimensionPixelOffset(R.dimen.addondetail_poster_width); + int posterHeight = resources.getDimensionPixelOffset(R.dimen.addondetail_poster_height); + + UIUtils.loadImageIntoImageview(hostManager, + TextUtils.isEmpty(fanart)? poster : fanart, + mediaArt, artWidth, artHeight); + UIUtils.loadImageIntoImageview(hostManager, + poster, + mediaPoster, posterWidth, posterHeight); - setupEnableButton(addonDetails.enabled); } private void setupEnableButton(boolean enabled) { // Enabled button if (enabled) { Resources.Theme theme = getActivity().getTheme(); - TypedArray styledAttributes = theme.obtainStyledAttributes(new int[] { + TypedArray styledAttributes = theme.obtainStyledAttributes(new int[]{ R.attr.colorAccent}); enabledButton.setColorFilter(styledAttributes.getColor(0, - getActivity().getResources().getColor(R.color.accent_default))); + getActivity().getResources().getColor(R.color.accent_default))); styledAttributes.recycle(); fabButton.setVisibility(View.VISIBLE); @@ -298,4 +277,16 @@ public class AddonDetailsFragment extends Fragment { } enabledButton.setTag(enabled); } + + /** + * Returns the shared element if visible + * @return View if visible, null otherwise + */ + public View getSharedElement() { + if (UIUtils.isViewInBounds(mediaPanel, mediaPoster)) { + return mediaPoster; + } + + return null; + } } diff --git a/app/src/main/java/org/xbmc/kore/ui/AddonListFragment.java b/app/src/main/java/org/xbmc/kore/ui/AddonListFragment.java index 7c6a331..5f1bbc5 100644 --- a/app/src/main/java/org/xbmc/kore/ui/AddonListFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/AddonListFragment.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.res.Resources; @@ -39,6 +40,7 @@ import org.xbmc.kore.jsonrpc.method.Addons; import org.xbmc.kore.jsonrpc.type.AddonType; import org.xbmc.kore.utils.LogUtils; import org.xbmc.kore.utils.UIUtils; +import org.xbmc.kore.utils.Utils; import java.util.List; @@ -53,7 +55,7 @@ public class AddonListFragment extends Fragment private static final String TAG = LogUtils.makeLogTag(AddonListFragment.class); public interface OnAddonSelectedListener { - public void onAddonSelected(String addonId, String addonTitle); + public void onAddonSelected(ViewHolder vh); } // Activity listener @@ -99,7 +101,7 @@ public class AddonListFragment extends Fragment // Get the movie id from the tag ViewHolder tag = (ViewHolder) view.getTag(); // Notify the activity - listenerActivity.onAddonSelected(tag.addonId, tag.addonName); + listenerActivity.onAddonSelected(tag); } }); @@ -210,8 +212,8 @@ public class AddonListFragment extends Fragment // To prevent the empty text from appearing on the first load, set it now emptyView.setText(getString(R.string.no_addons_found_refresh)); Toast.makeText(getActivity(), - String.format(getString(R.string.error_getting_addon_info), description), - Toast.LENGTH_SHORT).show(); + String.format(getString(R.string.error_getting_addon_info), description), + Toast.LENGTH_SHORT).show(); swipeRefreshLayout.setRefreshing(false); } }, callbackHandler); @@ -230,13 +232,12 @@ public class AddonListFragment extends Fragment // Use the same dimensions as in the details fragment, so that it hits Picasso's cache when // the user transitions to that fragment, avoiding another call and imediatelly showing the image Resources resources = context.getResources(); - artWidth = (int)(resources.getDimension(R.dimen.addondetail_poster_width) / - UIUtils.IMAGE_RESIZE_FACTOR); - artHeight = (int)(resources.getDimension(R.dimen.addondetail_poster_heigth) / - UIUtils.IMAGE_RESIZE_FACTOR); + artWidth = resources.getDimensionPixelOffset(R.dimen.addondetail_poster_width);; + artHeight = resources.getDimensionPixelOffset(R.dimen.addondetail_poster_height);; } /** {@inheritDoc} */ + @TargetApi(21) @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { @@ -257,13 +258,23 @@ public class AddonListFragment extends Fragment // Save the movie id viewHolder.addonId = addonDetails.addonid; viewHolder.addonName = addonDetails.name; + viewHolder.author = addonDetails.author; + viewHolder.description = addonDetails.description; + viewHolder.summary = addonDetails.summary; + viewHolder.version = addonDetails.version; + viewHolder.fanart = addonDetails.fanart; + viewHolder.poster = addonDetails.thumbnail; viewHolder.titleView.setText(viewHolder.addonName); viewHolder.detailsView.setText(addonDetails.summary); UIUtils.loadImageWithCharacterAvatar(getContext(), hostManager, - addonDetails.thumbnail, viewHolder.addonName, - viewHolder.artView, artWidth, artHeight); + addonDetails.thumbnail, viewHolder.addonName, + viewHolder.artView, artWidth, artHeight); + + if(Utils.isLollipopOrLater()) { + viewHolder.artView.setTransitionName("a"+viewHolder.addonId); + } return convertView; } } @@ -271,12 +282,18 @@ public class AddonListFragment extends Fragment /** * View holder pattern */ - private static class ViewHolder { + public static class ViewHolder { TextView titleView; TextView detailsView; ImageView artView; String addonId; String addonName; + String summary; + String author; + String version; + String description; + String fanart; + String poster; } } diff --git a/app/src/main/java/org/xbmc/kore/ui/AddonsActivity.java b/app/src/main/java/org/xbmc/kore/ui/AddonsActivity.java index 306e7e6..5bd3fc6 100644 --- a/app/src/main/java/org/xbmc/kore/ui/AddonsActivity.java +++ b/app/src/main/java/org/xbmc/kore/ui/AddonsActivity.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 Addons information (list, details) * All the information is presented by specific fragments @@ -47,6 +52,8 @@ public class AddonsActivity extends BaseActivity private NavigationDrawerFragment navigationDrawerFragment; + private boolean clearSharedElements; + @TargetApi(21) @Override protected void onCreate(Bundle savedInstanceState) { @@ -67,10 +74,26 @@ public class AddonsActivity extends BaseActivity // Setup animations if (Utils.isLollipopOrLater()) { - addonListFragment.setExitTransition(null); - addonListFragment.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); + addonListFragment.setExitTransition(fade); + addonListFragment.setReenterTransition(fade); + addonListFragment.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; + } + } + }; + addonListFragment.setExitSharedElementCallback(seCallback); } getSupportFragmentManager() .beginTransaction() @@ -175,32 +198,56 @@ public class AddonsActivity extends BaseActivity /** * Callback from list fragment when a addon is selected. * Switch fragment in portrait - * @param addonId Addon selected - * @param addonTitle Title + * @param vh */ @TargetApi(21) - public void onAddonSelected(String addonId, String addonTitle) { - selectedAddonId = addonId; - selectedAddonTitle = addonTitle; + public void onAddonSelected(AddonListFragment.ViewHolder vh) { + selectedAddonId = vh.addonId; + selectedAddonTitle = vh.addonName; // Replace list fragment - AddonDetailsFragment addonDetailsFragment = AddonDetailsFragment.newInstance(addonId); + final AddonDetailsFragment addonDetailsFragment = AddonDetailsFragment.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 (addonDetailsFragment.isVisible()) { + View sharedView = addonDetailsFragment.getSharedElement(); + if (sharedView == null) { // shared element not visible + clearSharedElements = true; + } + } + } + }; + addonDetailsFragment.setEnterSharedElementCallback(seCallback); + addonDetailsFragment.setEnterTransition(TransitionInflater - .from(this) - .inflateTransition(R.transition.media_details)); + .from(this) + .inflateTransition(R.transition.media_details)); addonDetailsFragment.setReturnTransition(null); + + Transition changeImageTransition = TransitionInflater.from( + this).inflateTransition(R.transition.change_image); + addonDetailsFragment.setSharedElementReturnTransition(changeImageTransition); + addonDetailsFragment.setSharedElementEnterTransition(changeImageTransition); + + fragTrans.addSharedElement(vh.artView, vh.artView.getTransitionName()); } else { fragTrans.setCustomAnimations(R.anim.fragment_details_enter, 0, - R.anim.fragment_list_popenter, 0); + R.anim.fragment_list_popenter, 0); } fragTrans.replace(R.id.fragment_container, addonDetailsFragment) - .addToBackStack(null) - .commit(); + .addToBackStack(null) + .commit(); setupActionBar(selectedAddonTitle); } diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml index 012545d..ea7d9e8 100644 --- a/app/src/main/res/values-sw600dp/dimens.xml +++ b/app/src/main/res/values-sw600dp/dimens.xml @@ -69,7 +69,7 @@ 88dp 146dp - 146dp + 146dp 88dp 88dp diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 875f988..fbc7249 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -114,7 +114,7 @@ 74dp 112dp - 112dp + 112dp 52dp 76dp