package com.distantfuture.videos.mainactivity; import android.app.Activity; import android.app.Fragment; import android.app.LoaderManager; import android.app.SearchManager; import android.app.SearchableInfo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.CursorLoader; import android.content.Loader; import android.content.res.TypedArray; import android.database.Cursor; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcelable; import android.text.TextUtils; 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.AbsListView; import android.widget.GridView; import android.widget.SearchView; import com.cocosw.undobar.UndoBarController; import com.distantfuture.videos.R; import com.distantfuture.videos.content.Content; import com.distantfuture.videos.database.Database; import com.distantfuture.videos.database.DatabaseAccess; import com.distantfuture.videos.database.DatabaseTables; import com.distantfuture.videos.database.YouTubeContentProvider; import com.distantfuture.videos.database.YouTubeData; import com.distantfuture.videos.imageutils.ToolbarIcons; import com.distantfuture.videos.misc.AppUtils; import com.distantfuture.videos.misc.BusEvents; import com.distantfuture.videos.misc.ContractFragment; import com.distantfuture.videos.misc.EmptyListHelper; import com.distantfuture.videos.misc.ScrollTriggeredAnimator; import com.distantfuture.videos.misc.Utils; import com.distantfuture.videos.services.ListServiceRequest; import com.distantfuture.videos.services.YouTubeService; import com.distantfuture.videos.youtube.VideoPlayer; import com.google.common.primitives.Longs; import com.nhaarman.listviewanimations.itemmanipulation.OnDismissCallback; import com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.SwipeDismissAdapter; import com.nhaarman.listviewanimations.swinginadapters.prepared.SwingBottomInAnimationAdapter; import java.util.ArrayList; import de.greenrobot.event.EventBus; import uk.co.senab.actionbarpulltorefresh.library.ActionBarPullToRefresh; import uk.co.senab.actionbarpulltorefresh.library.Options; import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshLayout; import uk.co.senab.actionbarpulltorefresh.library.listeners.OnRefreshListener; public class YouTubeGridFragment extends ContractFragment<DrawerActivitySupport> implements OnRefreshListener, OnDismissCallback, YouTubeCursorAdapter.YouTubeCursorAdapterListener, LoaderManager.LoaderCallbacks<Cursor> { private EmptyListHelper mEmptyListHelper; private ListServiceRequest mRequest; private YouTubeCursorAdapter mAdapter; private PullToRefreshLayout mPullToRefreshLayout; private boolean mCachedHiddenPref; private BroadcastReceiver mBroadcastReceiver; private String mFilter; private MenuItem mSearchItem; private SearchView mSearchView; private Drawable mSearchDrawable; private boolean mSearchSubmitted = false; public static YouTubeGridFragment newInstance(ListServiceRequest request) { YouTubeGridFragment fragment = new YouTubeGridFragment(); Bundle args = new Bundle(); args.putBundle("request", request.toBundle()); fragment.setArguments(args); return fragment; } public void syncActionBarTitle() { DrawerActivitySupport provider = getContract(); if (provider != null) { // activity can control the actionbar title, for example the player sets the Now Playing title if (!provider.actionBarTitleHandled()) { String subtitle = null; String title = null; if (mRequest != null) { int cnt = mAdapter.getCount(); subtitle = String.format("%d ", cnt) + mRequest.unitName(cnt > 1 || cnt == 0); String channelName = Content.instance().channelName(); if (channelName != null) title = channelName; String containerName = mRequest.containerName(); if (containerName != null) subtitle += ": " + containerName; } provider.setActionBarTitle(title, subtitle); } } } private boolean endSearchActionBar() { mSearchSubmitted = true; // had to add this for KitKat? Maybe I'm doing something slightly non standard? if (mSearchItem.isActionViewExpanded()) return mSearchItem.collapseActionView(); return false; } @Override public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); // hide the search item if player visible DrawerActivitySupport provider = getContract(); if (provider != null) { if (provider.isPlayerVisible()) { mSearchItem.setVisible(false); } else { mSearchItem.setVisible(true); } } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.youtube_fragment_menu, menu); setupSearchItem(menu); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); EventBus.getDefault().register(this); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { GridView gridView; mRequest = ListServiceRequest.fromBundle(getArguments().getBundle("request")); mAdapter = YouTubeCursorAdapter.newAdapter(getActivity(), mRequest, this); ViewGroup rootView = mAdapter.rootView(container); // Now find the PullToRefreshLayout to setup mPullToRefreshLayout = (PullToRefreshLayout) rootView.findViewById(R.id.grid_frame_layout); Options.Builder options = new Options.Builder(); options.scrollDistance(0.4f); // Now setup the PullToRefreshLayout ActionBarPullToRefresh.from(this.getActivity()) // Mark All Children as pullable .allChildrenArePullable() // Set the OnRefreshListener .listener(this) .options(options.build()) // Finally commit the setup to our PullToRefreshLayout .setup(mPullToRefreshLayout); gridView = (GridView) rootView.findViewById(R.id.gridview); gridView.setOnItemClickListener(mAdapter); SwingBottomInAnimationAdapter swingBottomInAnimationAdapter = new SwingBottomInAnimationAdapter(new SwipeDismissAdapter(mAdapter, this)); swingBottomInAnimationAdapter.setInitialDelayMillis(200); swingBottomInAnimationAdapter.setAbsListView(gridView); gridView.setAdapter(swingBottomInAnimationAdapter); // setup empty view mEmptyListHelper = new EmptyListHelper(rootView.findViewById(R.id.empty_view)); gridView.setEmptyView(mEmptyListHelper.view()); // create the loader getLoaderManager().initLoader(0, null, this); // dimmer only exists for dark mode View dimmerView = rootView.findViewById(R.id.dimmer); if (dimmerView != null) new ScrollTriggeredAnimator(gridView, dimmerView); return rootView; } // OnDismissCallback @Override public void onDismiss(AbsListView listView, int[] reverseSortedPositions) { ArrayList<Long> ids = new ArrayList<Long>(); for (int position : reverseSortedPositions) { Cursor cursor = (Cursor) mAdapter.getItem(position); YouTubeData itemMap = mRequest.databaseTable().cursorToItem(cursor, null); DatabaseAccess database = new DatabaseAccess(getActivity(), mRequest); ids.add(itemMap.mID); if (itemMap != null) { boolean deleteOnHide = false; // true for debugging only if (deleteOnHide) database.deleteItem(itemMap.mID); else { itemMap.setHidden(!itemMap.isHidden()); database.updateItem(itemMap); } } } UndoBarController.UndoListener listener = new UndoBarController.UndoListener() { @Override public void onUndo(Parcelable parcelable) { // was getting crashes here, this fixed it. I think quickly double tapping the undo button triggers this if (parcelable == null) { return; } DatabaseAccess database = new DatabaseAccess(getActivity(), mRequest); Bundle info = (Bundle) parcelable; long ids[] = info.getLongArray("id_array"); for (long id : ids) { YouTubeData itemMap = database.getItemWithID(id); if (itemMap != null) { itemMap.setHidden(!itemMap.isHidden()); database.updateItem(itemMap); } } } }; Bundle info = new Bundle(); info.putLongArray("id_array", Longs.toArray(ids)); UndoBarController.show(getActivity(), mRequest.unitName(false) + " hidden", listener, info); } public String getFilter() { return mFilter; } public boolean setFilter(String filter) { if (!TextUtils.equals(mFilter, filter)) { mFilter = filter; getLoaderManager().restartLoader(0, null, this); return true; } return false; } // YouTubeCursorAdapterListener @Override public void adapterDataChanged() { syncActionBarTitle(); if (!TextUtils.isEmpty(getFilter()) && mSearchItem.isActionViewExpanded()) { int cnt = mAdapter.getCount(); Utils.message(getActivity(), String.format("Found: %d items", cnt)); } } // YouTubeCursorAdapterListener @Override public void handleClickFromAdapter(int position, YouTubeData itemMap) { DrawerActivitySupport provider = getContract(); // get rid of the search if still open. Someone could search // and click a visible item rather than hitting the search button on keyboard endSearchActionBar(); switch (mRequest.type()) { case RELATED: case SEARCH: case LIKED: case VIDEOS: VideoPlayer.PlayerParams params = new VideoPlayer.PlayerParams(itemMap.mVideo, itemMap.mTitle, position); if (provider != null) provider.playVideo(params); break; case PLAYLISTS: { String playlistID = itemMap.mPlaylist; if (playlistID != null) { Fragment frag = YouTubeGridFragment.newInstance(ListServiceRequest.videosRequest(playlistID, itemMap.mTitle)); if (provider != null) provider.installFragment(frag, true); } } break; } } // YouTubeCursorAdapterListener @Override public Activity accesActivity() { return getActivity(); } @Override public void onDestroy() { EventBus.getDefault().unregister(this); super.onDestroy(); } @Override public void onResume() { super.onResume(); // reload if the hidden pref is now how we remember it reloadForPrefChange(); syncActionBarTitle(); } // for EventBus public void onEventMainThread(BusEvents.PlayNextEvent event) { int index = event.params.index; int cnt = mAdapter.getCount(); index++; // go to next if (index > cnt) { index = 0; // loop around } if (index < cnt) { Cursor cursor = (Cursor) mAdapter.getItem(index); YouTubeData itemMap = mRequest.databaseTable().cursorToItem(cursor, null); if (itemMap != null) { DrawerActivitySupport provider = getContract(); if (provider != null) { VideoPlayer.PlayerParams params = new VideoPlayer.PlayerParams(itemMap.mVideo, itemMap.mTitle, index); provider.playVideo(params); } } } } public void onEventMainThread(BusEvents.YouTubeFragmentDataReady event) { // stop the pull to refresh indicator // Notify PullToRefreshLayout that the refresh has finished mPullToRefreshLayout.setRefreshComplete(); // in the case of no results, we need to update the emptylist view to reflect that // This only shows up at launch, or the first time a list is requested mEmptyListHelper.updateEmptyListView("List is Empty", true); } // eventbus event public void onEventMainThread(BusEvents.ContentEvent event) { // need to update the channel name in the action bar once ready syncActionBarTitle(); } // OnRefreshListener @Override public void onRefreshStarted(View view) { YouTubeService.startListRequest(getActivity(), mRequest, true); } // called by activity on menu item action for show hidden files toggle public void reloadForPrefChange() { boolean showHidden = AppUtils.instance(getActivity()).showHiddenItems(); if (mCachedHiddenPref != showHidden) { mCachedHiddenPref = showHidden; getLoaderManager().restartLoader(0, null, this); } } @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { DatabaseTables.DatabaseTable table = mRequest.databaseTable(); // Debug.log(mRequest.toString()); String sortOrder = (DatabaseTables.videoTable() == table) ? "vi" : "pl"; // stupid hack mCachedHiddenPref = AppUtils.instance(getActivity()).showHiddenItems(); int queryID = DatabaseTables.VISIBLE_ITEMS; if (mCachedHiddenPref) queryID = DatabaseTables.ALL_ITEMS; Database.DatabaseQuery queryParams = table.queryParams(queryID, mRequest.requestIdentifier(), mFilter); // startRequest below will notify when done and we hide the progress bar mEmptyListHelper.updateEmptyListView("Talking to YouTube...", false); YouTubeService.startListRequest(getActivity(), mRequest, false); return new CursorLoader(getActivity(), YouTubeContentProvider.contentsURI(getActivity()), queryParams.mProjection, queryParams.mSelection, queryParams.mSelectionArgs, sortOrder); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor c) { mAdapter.swapCursor(c); } @Override public void onLoaderReset(Loader<Cursor> arg0) { mAdapter.swapCursor(null); } private void setupSearchItem(Menu menu) { mSearchItem = menu.findItem(R.id.action_search); if (mSearchItem != null) { if (mSearchDrawable == null) { // seems insane, is this the best way of having a variable drawable resource by theme? int[] attrs = new int[]{R.attr.action_bar_icon_color}; TypedArray ta = getActivity().obtainStyledAttributes(attrs); int color = ta.getColor(0, 0); ta.recycle(); mSearchDrawable = ToolbarIcons.icon(getActivity(), ToolbarIcons.IconID.SEARCH, color, 32); mSearchDrawable.setAlpha(150); } mSearchItem.setIcon(mSearchDrawable); mSearchView = (SearchView) mSearchItem.getActionView(); // mSearchView.setSubmitButtonEnabled(true); mSearchView.setQueryHint("Search"); // not sure if this is needed or not yet.... SearchManager searchManager = (SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE); SearchableInfo searchableInfo = searchManager.getSearchableInfo(getActivity().getComponentName()); mSearchView.setSearchableInfo(searchableInfo); mSearchItem.setActionView(mSearchView); // change the text color inside the searchView, There is no way to theme this shitty thing int textColor = getActivity().getResources().getColor(android.R.color.primary_text_dark); int hintColor = getActivity().getResources().getColor(android.R.color.secondary_text_dark); Utils.textViewColorChanger(mSearchView, textColor, hintColor); mSearchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { @Override public boolean onMenuItemActionExpand(MenuItem item) { // return false and clear the filter if clicked when a filter is set if (!TextUtils.isEmpty(getFilter())) { // this is needed since if the action bar refreshes, it will restore the query, kitkat seems to clear this though mSearchView.setQuery(null, true); // added for KitKat, previously setQuery to null would trigger the text listener setFilter(null); return false; } return true; } @Override public boolean onMenuItemActionCollapse(MenuItem item) { return true; } }); mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { if (mSearchItem != null) { return endSearchActionBar(); } return false; } @Override public boolean onQueryTextChange(String newText) { // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. String filter = !TextUtils.isEmpty(newText) ? newText : null; boolean setFilter = true; if (mSearchSubmitted && filter == null) { // on KitKat collapseActionView call above on submit, sends null for newText, so preventing this from changing // the filter here setFilter = false; } mSearchSubmitted = false; // had to add this for KitKat? Maybe I'm doing something slightly non standard? return (setFilter && setFilter(filter)); } }); } } }