package tv.emby.embyatv.browsing; import android.app.Fragment; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.support.annotation.Nullable; import android.support.v17.leanback.app.BackgroundManager; import android.support.v17.leanback.app.RowsFragment; import android.support.v17.leanback.widget.ArrayObjectAdapter; import android.support.v17.leanback.widget.ClassPresenterSelector; import android.support.v17.leanback.widget.HeaderItem; import android.support.v17.leanback.widget.ListRow; import android.support.v17.leanback.widget.OnItemViewClickedListener; import android.support.v17.leanback.widget.OnItemViewSelectedListener; import android.support.v17.leanback.widget.Presenter; import android.support.v17.leanback.widget.Row; import android.support.v17.leanback.widget.RowPresenter; import android.util.DisplayMetrics; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.bumptech.glide.Glide; import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.SimpleTarget; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import mediabrowser.apiinteraction.EmptyResponse; import mediabrowser.apiinteraction.Response; import mediabrowser.model.dto.BaseItemDto; import mediabrowser.model.entities.DisplayPreferences; import tv.emby.embyatv.R; import tv.emby.embyatv.TvApp; import tv.emby.embyatv.base.BaseActivity; import tv.emby.embyatv.base.CustomMessage; import tv.emby.embyatv.base.IKeyListener; import tv.emby.embyatv.base.IMessageListener; import tv.emby.embyatv.details.ItemListActivity; import tv.emby.embyatv.itemhandling.BaseRowItem; import tv.emby.embyatv.itemhandling.ItemLauncher; import tv.emby.embyatv.itemhandling.ItemRowAdapter; import tv.emby.embyatv.playback.MediaManager; import tv.emby.embyatv.presentation.CardPresenter; import tv.emby.embyatv.presentation.GridButtonPresenter; import tv.emby.embyatv.presentation.PositionableListRowPresenter; import tv.emby.embyatv.querying.QueryType; import tv.emby.embyatv.querying.ViewQuery; import tv.emby.embyatv.ui.GridButton; import tv.emby.embyatv.util.InfoLayoutHelper; import tv.emby.embyatv.util.KeyProcessor; /** * Created by Eric on 5/17/2015. */ public class EnhancedBrowseFragment extends Fragment implements IRowLoader { private static final int BACKGROUND_UPDATE_DELAY = 100; Typeface roboto; BaseActivity mActivity; TvApp mApplication; TextView mTitle; LinearLayout mInfoRow; TextView mSummary; protected static final int BY_LETTER = 0; protected static final int GENRES = 1; protected static final int YEARS = 2; protected static final int PERSONS = 3; protected static final int SUGGESTED = 4; protected static final int SEARCH = 5; protected static final int GRID = 6; protected static final int ALBUMS = 7; protected static final int ARTISTS = 8; public static final int FAVSONGS = 9; protected BaseItemDto mFolder; protected String itemTypeString; protected boolean showViews = true; protected boolean justLoaded = true; protected BaseRowItem favSongsRowItem; private SimpleTarget<Bitmap> mBackgroundTarget; private DisplayMetrics mMetrics; RowsFragment mRowsFragment; protected CompositeClickedListener mClickedListener = new CompositeClickedListener(); protected CompositeSelectedListener mSelectedListener = new CompositeSelectedListener(); protected ArrayObjectAdapter mRowsAdapter; private Drawable mDefaultBackground; private Timer mBackgroundTimer; private final Handler mHandler = new Handler(); private String mBackgroundUrl; protected ArrayList<BrowseRowDef> mRows = new ArrayList<>(); CardPresenter mCardPresenter; protected BaseRowItem mCurrentItem; protected ListRow mCurrentRow; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); BaseItemDto item = new BaseItemDto(); item.setId(ItemListActivity.FAV_SONGS); item.setType("Playlist"); item.setIsFolder(true); favSongsRowItem = new BaseRowItem(0, item); } @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.enhanced_detail_browse, container, false); mActivity = (BaseActivity) getActivity(); roboto = TvApp.getApplication().getDefaultFont(); mTitle = (TextView) root.findViewById(R.id.title); mTitle.setTypeface(roboto); mTitle.setShadowLayer(5, 5, 5, Color.BLACK); mInfoRow = (LinearLayout) root.findViewById(R.id.infoRow); mSummary = (TextView) root.findViewById(R.id.summary); mSummary.setTypeface(roboto); mSummary.setShadowLayer(5, 5, 5, Color.BLACK); // Inject the RowsFragment in the results container if (getChildFragmentManager().findFragmentById(R.id.rowsFragment) == null) { mRowsFragment = new RowsFragment(); getChildFragmentManager().beginTransaction() .replace(R.id.rowsFragment, mRowsFragment).commit(); } else { mRowsFragment = (RowsFragment) getChildFragmentManager() .findFragmentById(R.id.rowsFragment); } mRowsAdapter = new ArrayObjectAdapter(new PositionableListRowPresenter()); mRowsFragment.setAdapter(mRowsAdapter); return root; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mApplication = TvApp.getApplication(); mDefaultBackground = mApplication.getDrawableCompat(R.drawable.blank10x10); prepareBackgroundManager(); setupViews(); setupUIElements(); setupQueries(this); setupEventListeners(); } protected void setupQueries(IRowLoader rowLoader) { rowLoader.loadRows(mRows); } protected void setupViews() { mFolder = TvApp.getApplication().getSerializer().DeserializeFromString(getActivity().getIntent().getStringExtra("Folder"),BaseItemDto.class); if (mFolder == null) return; if (mFolder.getCollectionType() != null) { switch (mFolder.getCollectionType()) { case "movies": itemTypeString = "Movie"; break; case "tvshows": itemTypeString = "Series"; break; case "music": itemTypeString = "MusicAlbum"; break; case "folders": showViews = false; break; default: showViews = false; } } else { showViews = false; } } @Override public void onDestroy() { super.onDestroy(); if (null != mBackgroundTimer) { mBackgroundTimer.cancel(); } } @Override public void onResume() { super.onResume(); //React to deletion if (getActivity() != null && !getActivity().isFinishing() && mCurrentRow != null && mCurrentItem != null && mCurrentItem.getItemId() != null && mCurrentItem.getItemId().equals(TvApp.getApplication().getLastDeletedItemId())) { ((ItemRowAdapter)mCurrentRow.getAdapter()).remove(mCurrentItem); TvApp.getApplication().setLastDeletedItemId(null); } if (!justLoaded) { //Re-retrieve anything that needs it but delay slightly so we don't take away gui landing if (mRowsAdapter != null) { refreshCurrentItem(); new Handler().postDelayed(new Runnable() { @Override public void run() { if (mActivity.isFinishing()) return; for (int i = 0; i < mRowsAdapter.size(); i++) { if (mRowsAdapter.get(i) instanceof ListRow) { if (((ListRow) mRowsAdapter.get(i)).getAdapter() instanceof ItemRowAdapter && !mActivity.isFinishing()) { ((ItemRowAdapter) ((ListRow) mRowsAdapter.get(i)).getAdapter()).ReRetrieveIfNeeded(); } } } } }, 1500); } } else { justLoaded = false; } } public void loadRows(List<BrowseRowDef> rows) { mRowsAdapter = new ArrayObjectAdapter(new PositionableListRowPresenter()); mCardPresenter = new CardPresenter(false, 280); ClassPresenterSelector ps = new ClassPresenterSelector(); ps.addClassPresenter(BaseRowItem.class, mCardPresenter); ps.addClassPresenter(GridButton.class, new GridButtonPresenter(false, 310, 280)); for (BrowseRowDef def : rows) { HeaderItem header = new HeaderItem(def.getHeaderText()); ItemRowAdapter rowAdapter; switch (def.getQueryType()) { case NextUp: rowAdapter = new ItemRowAdapter(def.getNextUpQuery(), true, mCardPresenter, mRowsAdapter); break; case Season: rowAdapter = new ItemRowAdapter(def.getSeasonQuery(), mCardPresenter, mRowsAdapter); break; case Upcoming: rowAdapter = new ItemRowAdapter(def.getUpcomingQuery(), mCardPresenter, mRowsAdapter); break; case Views: rowAdapter = new ItemRowAdapter(new ViewQuery(), mCardPresenter, mRowsAdapter); break; case SimilarSeries: rowAdapter = new ItemRowAdapter(def.getSimilarQuery(), QueryType.SimilarSeries, mCardPresenter, mRowsAdapter); break; case SimilarMovies: rowAdapter = new ItemRowAdapter(def.getSimilarQuery(), QueryType.SimilarMovies, mCardPresenter, mRowsAdapter); break; case Persons: rowAdapter = new ItemRowAdapter(def.getPersonsQuery(), def.getChunkSize(), mCardPresenter, mRowsAdapter); break; case LiveTvChannel: rowAdapter = new ItemRowAdapter(def.getTvChannelQuery(), 40, mCardPresenter, mRowsAdapter); break; case LiveTvProgram: rowAdapter = new ItemRowAdapter(def.getProgramQuery(), mCardPresenter, mRowsAdapter); break; case LiveTvRecording: rowAdapter = new ItemRowAdapter(def.getRecordingQuery(), mCardPresenter, mRowsAdapter); break; case LiveTvRecordingGroup: rowAdapter = new ItemRowAdapter(def.getRecordingGroupQuery(), mCardPresenter, mRowsAdapter); break; case Premieres: rowAdapter = new ItemRowAdapter(def.getQuery(), def.getChunkSize(), def.getPreferParentThumb(), def.isStaticHeight(), mCardPresenter, mRowsAdapter, def.getQueryType()); break; default: rowAdapter = new ItemRowAdapter(def.getQuery(), def.getChunkSize(), def.getPreferParentThumb(), def.isStaticHeight(), ps, mRowsAdapter, def.getQueryType()); break; } rowAdapter.setReRetrieveTriggers(def.getChangeTriggers()); ListRow row = new ListRow(header, rowAdapter); mRowsAdapter.add(row); rowAdapter.setRow(row); rowAdapter.Retrieve(); } addAdditionalRows(mRowsAdapter); mRowsFragment.setAdapter(mRowsAdapter); } protected void addAdditionalRows(ArrayObjectAdapter rowAdapter) { if (showViews) { HeaderItem gridHeader = new HeaderItem(rowAdapter.size(), mApplication.getString(R.string.lbl_views)); GridButtonPresenter mGridPresenter = new GridButtonPresenter(); ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter); switch (itemTypeString) { case "Movie": gridRowAdapter.add(new GridButton(SUGGESTED, mApplication.getString(R.string.lbl_suggested), R.drawable.suggestions)); addStandardViewButtons(gridRowAdapter); break; case "MusicAlbum": gridRowAdapter.add(new GridButton(ALBUMS, TvApp.getApplication().getString(R.string.lbl_albums), R.drawable.audio)); gridRowAdapter.add(new GridButton(ARTISTS, TvApp.getApplication().getString(R.string.lbl_artists), R.drawable.artists)); gridRowAdapter.add(new GridButton(GENRES, mApplication.getString(R.string.lbl_genres), R.drawable.genres)); gridRowAdapter.add(new GridButton(SEARCH, mApplication.getString(R.string.lbl_search), R.drawable.search)); break; default: addStandardViewButtons(gridRowAdapter); break; } rowAdapter.add(new ListRow(gridHeader, gridRowAdapter)); } } protected void addStandardViewButtons(ArrayObjectAdapter gridRowAdapter) { gridRowAdapter.add(new GridButton(GRID, TvApp.getApplication().getString(R.string.lbl_all_items), R.drawable.grid)); gridRowAdapter.add(new GridButton(BY_LETTER, mApplication.getString(R.string.lbl_by_letter), R.drawable.byletter)); gridRowAdapter.add(new GridButton(GENRES, mApplication.getString(R.string.lbl_genres), R.drawable.genres)); gridRowAdapter.add(new GridButton(PERSONS, mApplication.getString(R.string.lbl_performers), R.drawable.actors)); gridRowAdapter.add(new GridButton(SEARCH, mApplication.getString(R.string.lbl_search), R.drawable.search)); } protected void setupUIElements() { } protected void setupEventListeners() { mRowsFragment.setOnItemViewClickedListener(mClickedListener); mClickedListener.registerListener(new ItemViewClickedListener()); if (showViews) mClickedListener.registerListener(new SpecialViewClickedListener()); mRowsFragment.setOnItemViewSelectedListener(mSelectedListener); mSelectedListener.registerListener(new ItemViewSelectedListener()); if (mActivity != null) { mActivity.registerKeyListener(new IKeyListener() { @Override public boolean onKeyUp(int key, KeyEvent event) { return KeyProcessor.HandleKey(key, mCurrentItem, mActivity); } }); mActivity.registerMessageListener(new IMessageListener() { @Override public void onMessageReceived(CustomMessage message) { switch (message) { case RefreshCurrentItem: refreshCurrentItem(); break; } } }); } } private void refreshCurrentItem() { if (mCurrentItem != null && !"Photo".equals(mCurrentItem.getType()) && !"MusicArtist".equals(mCurrentItem.getType()) && !"MusicAlbum".equals(mCurrentItem.getType()) && !"Playlist".equals(mCurrentItem.getType())) { TvApp.getApplication().getLogger().Debug("Refresh item "+mCurrentItem.getFullName()); mCurrentItem.refresh(new EmptyResponse() { @Override public void onResponse() { ItemRowAdapter adapter = (ItemRowAdapter) ((ListRow) mCurrentRow).getAdapter(); adapter.notifyArrayItemRangeChanged(adapter.indexOf(mCurrentItem), 1); } }); } } private final class SpecialViewClickedListener implements OnItemViewClickedListener { @Override public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { if (item instanceof GridButton) { switch (((GridButton) item).getId()) { case GRID: TvApp.getApplication().getDisplayPrefsAsync(mFolder.getDisplayPreferencesId(), new Response<DisplayPreferences>() { @Override public void onResponse(DisplayPreferences response) { Intent folderIntent = new Intent(getActivity(), GenericGridActivity.class); folderIntent.putExtra("Folder", TvApp.getApplication().getSerializer().SerializeToString(mFolder)); getActivity().startActivity(folderIntent); } }); break; case ALBUMS: mFolder.setDisplayPreferencesId(mFolder.getId()+"AL"); TvApp.getApplication().getDisplayPrefsAsync(mFolder.getDisplayPreferencesId(), new Response<DisplayPreferences>() { @Override public void onResponse(DisplayPreferences response) { Intent folderIntent = new Intent(getActivity(), GenericGridActivity.class); folderIntent.putExtra("Folder", TvApp.getApplication().getSerializer().SerializeToString(mFolder)); folderIntent.putExtra("IncludeType", "MusicAlbum"); getActivity().startActivity(folderIntent); } }); break; case ARTISTS: mFolder.setDisplayPreferencesId(mFolder.getId()+"AR"); TvApp.getApplication().getDisplayPrefsAsync(mFolder.getDisplayPreferencesId(), new Response<DisplayPreferences>() { @Override public void onResponse(DisplayPreferences response) { Intent folderIntent = new Intent(getActivity(), GenericGridActivity.class); folderIntent.putExtra("Folder", TvApp.getApplication().getSerializer().SerializeToString(mFolder)); folderIntent.putExtra("IncludeType", "AlbumArtist"); getActivity().startActivity(folderIntent); } }); break; case BY_LETTER: Intent intent = new Intent(getActivity(), ByLetterActivity.class); intent.putExtra("Folder", TvApp.getApplication().getSerializer().SerializeToString(mFolder)); intent.putExtra("IncludeType", itemTypeString); getActivity().startActivity(intent); break; case GENRES: Intent genreIntent = new Intent(getActivity(), ByGenreActivity.class); genreIntent.putExtra("Folder", TvApp.getApplication().getSerializer().SerializeToString(mFolder)); genreIntent.putExtra("IncludeType", itemTypeString); getActivity().startActivity(genreIntent); break; case SUGGESTED: Intent suggIntent = new Intent(getActivity(), SuggestedMoviesActivity.class); suggIntent.putExtra("Folder", TvApp.getApplication().getSerializer().SerializeToString(mFolder)); suggIntent.putExtra("IncludeType", itemTypeString); getActivity().startActivity(suggIntent); break; case PERSONS: Intent personIntent = new Intent(getActivity(), BrowsePersonsActivity.class); personIntent.putExtra("Folder", TvApp.getApplication().getSerializer().SerializeToString(mFolder)); personIntent.putExtra("IncludeType", itemTypeString); getActivity().startActivity(personIntent); break; case SEARCH: TvApp.getApplication().showSearch(getActivity(), "MusicAlbum".equals(itemTypeString)); break; case FAVSONGS: Intent favIntent = new Intent(getActivity(), ItemListActivity.class); favIntent.putExtra("ItemId", ItemListActivity.FAV_SONGS); favIntent.putExtra("ParentId", mFolder.getId()); getActivity().startActivity(favIntent); break; default: Toast.makeText(getActivity(), item.toString() + mApplication.getString(R.string.msg_not_implemented), Toast.LENGTH_SHORT) .show(); break; } } } } private final class ItemViewClickedListener implements OnItemViewClickedListener { @Override public void onItemClicked(final Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { if (!(item instanceof BaseRowItem)) return; MediaManager.setCurrentMediaTitle(row.getHeaderItem().getName()); ItemLauncher.launch((BaseRowItem) item, (ItemRowAdapter) ((ListRow)row).getAdapter(), ((BaseRowItem)item).getIndex(), getActivity()); } } private final class ItemViewSelectedListener implements OnItemViewSelectedListener { @Override public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { mHandler.removeCallbacks(updateContentTask); if (item instanceof GridButton && ((GridButton)item).getId() == FAVSONGS) { //set to specialized item mCurrentItem = favSongsRowItem; } if (!(item instanceof BaseRowItem)) { mTitle.setText(mFolder.getName()); mInfoRow.removeAllViews(); mSummary.setText(""); //fill in default background mBackgroundUrl = null; startBackgroundTimer(); return; } BaseRowItem rowItem = (BaseRowItem) item; mCurrentItem = rowItem; mCurrentRow = (ListRow) row; mTitle.setText(mCurrentItem.getName()); mInfoRow.removeAllViews(); mSummary.setText(""); mHandler.postDelayed(updateContentTask, 500); //mApplication.getLogger().Debug("Selected Item "+rowItem.getIndex() + " type: "+ (rowItem.getItemType().equals(BaseRowItem.ItemType.BaseItem) ? rowItem.getBaseItem().getType() : "other")); ItemRowAdapter adapter = (ItemRowAdapter) ((ListRow)row).getAdapter(); adapter.loadMoreItemsIfNeeded(rowItem.getIndex()); mBackgroundUrl = rowItem.getBackdropImageUrl(); startBackgroundTimer(); } } protected Runnable updateContentTask = new Runnable() { @Override public void run() { if (mCurrentItem == null) return; mTitle.setText(mCurrentItem.getName()); mSummary.setText(mCurrentItem.getSummary()); InfoLayoutHelper.addInfoRow(mActivity, mCurrentItem, mInfoRow, true, true); } }; private void prepareBackgroundManager() { final BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity()); backgroundManager.attach(getActivity().getWindow()); mDefaultBackground = getResources().getDrawable(R.drawable.moviebg); mMetrics = new DisplayMetrics(); getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics); mBackgroundTarget = new SimpleTarget<Bitmap>(mMetrics.widthPixels, mMetrics.heightPixels) { @Override public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) { backgroundManager.setBitmap(resource); } }; } protected void updateBackground(String url) { if (url == null) { clearBackground(); } else { Glide.with(getActivity()) .load(url) .asBitmap() .override(mMetrics.widthPixels, mMetrics.heightPixels) .centerCrop() .error(mDefaultBackground) .into(mBackgroundTarget); } } protected void updateBackground(Drawable drawable) { BackgroundManager.getInstance(getActivity()).setDrawable(drawable); } protected void clearBackground() { BackgroundManager.getInstance(getActivity()).setDrawable(mDefaultBackground); } private void startBackgroundTimer() { if (null != mBackgroundTimer) { mBackgroundTimer.cancel(); } mBackgroundTimer = new Timer(); mBackgroundTimer.schedule(new UpdateBackgroundTask(), BACKGROUND_UPDATE_DELAY); } private class UpdateBackgroundTask extends TimerTask { @Override public void run() { mHandler.post(new Runnable() { @Override public void run() { if (mBackgroundUrl != null) { updateBackground(mBackgroundUrl); } else { updateBackground(mDefaultBackground); } } }); } } }