package com.hitherejoe.androidtvboilerplate.ui.search; import android.Manifest; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.support.v17.leanback.app.BackgroundManager; import android.support.v17.leanback.app.SearchFragment; import android.support.v17.leanback.widget.ArrayObjectAdapter; import android.support.v17.leanback.widget.HeaderItem; import android.support.v17.leanback.widget.ListRow; import android.support.v17.leanback.widget.ListRowPresenter; import android.support.v17.leanback.widget.ObjectAdapter; 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.support.v17.leanback.widget.SpeechRecognitionCallback; import android.support.v4.content.ContextCompat; import android.text.TextUtils; import android.util.DisplayMetrics; import android.widget.Toast; import com.bumptech.glide.Glide; import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.SimpleTarget; import com.hitherejoe.androidtvboilerplate.R; import com.hitherejoe.androidtvboilerplate.data.model.Cat; import com.hitherejoe.androidtvboilerplate.ui.base.BaseActivity; import com.hitherejoe.androidtvboilerplate.ui.common.CardPresenter; import com.hitherejoe.androidtvboilerplate.util.NetworkUtil; import com.hitherejoe.androidtvboilerplate.util.ToastFactory; import java.net.URI; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; import timber.log.Timber; public class SearchContentFragment extends SearchFragment implements SearchContentMvpView, SearchFragment.SearchResultProvider { @Inject SearchContentPresenter mSearchContentPresenter; private static final int BACKGROUND_UPDATE_DELAY = 300; private static final int REQUEST_SPEECH = 0x00000010; private ArrayObjectAdapter mResultsAdapter; private ArrayObjectAdapter mSearchObjectAdapter; private BackgroundManager mBackgroundManager; private Drawable mDefaultBackground; private DisplayMetrics mMetrics; private Handler mHandler; private Runnable mBackgroundRunnable; private String mSearchQuery; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ((BaseActivity) getActivity()).activityComponent().inject(this); mResultsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); mHandler = new Handler(); mSearchContentPresenter.attachView(this); setSearchResultProvider(this); setupBackgroundManager(); setListeners(); } public void onDestroy() { if (mBackgroundRunnable != null) { mHandler.removeCallbacks(mBackgroundRunnable); mBackgroundRunnable = null; } mBackgroundManager = null; mSearchContentPresenter.detachView(); super.onDestroy(); } @Override public void onStop() { super.onStop(); mBackgroundManager.release(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_SPEECH: switch (resultCode) { case Activity.RESULT_OK: setSearchQuery(data, false); break; case Activity.RESULT_CANCELED: Timber.i("Recognizer canceled"); break; } break; } } @Override public ObjectAdapter getResultsAdapter() { return mResultsAdapter; } @Override public boolean onQueryTextChange(String newQuery) { loadQuery(newQuery); return true; } @Override public boolean onQueryTextSubmit(String query) { loadQuery(query); return true; } public boolean hasResults() { return mResultsAdapter.size() > 0; } protected void updateBackground(String uri) { int width = mMetrics.widthPixels; int height = mMetrics.heightPixels; Glide.with(getActivity()) .load(uri) .asBitmap() .centerCrop() .error(mDefaultBackground) .into(new SimpleTarget<Bitmap>(width, height) { @Override public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) { mBackgroundManager.setBitmap(resource); } }); if (mBackgroundRunnable != null) mHandler.removeCallbacks(mBackgroundRunnable); } private void setupBackgroundManager() { mBackgroundManager = BackgroundManager.getInstance(getActivity()); mBackgroundManager.attach(getActivity().getWindow()); mBackgroundManager.setColor(ContextCompat.getColor(getActivity(), R.color.primary_light)); mDefaultBackground = new ColorDrawable(ContextCompat.getColor(getActivity(), R.color.primary_light)); mMetrics = new DisplayMetrics(); getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics); } private void startBackgroundTimer(final URI backgroundURI) { if (mBackgroundRunnable != null) mHandler.removeCallbacks(mBackgroundRunnable); mBackgroundRunnable = new Runnable() { @Override public void run() { if (backgroundURI != null) updateBackground(backgroundURI.toString()); } }; mHandler.postDelayed(mBackgroundRunnable, BACKGROUND_UPDATE_DELAY); } private void setListeners() { setOnItemViewClickedListener(mOnItemViewClickedListener); setOnItemViewSelectedListener(mOnItemViewSelectedListener); if (!hasPermission(Manifest.permission.RECORD_AUDIO)) { setSpeechRecognitionCallback(new SpeechRecognitionCallback() { @Override public void recognizeSpeech() { try { startActivityForResult(getRecognizerIntent(), REQUEST_SPEECH); } catch (ActivityNotFoundException error) { Timber.e(error, "Cannot find activity for speech recognizer"); } } }); } } private boolean hasPermission(final String permission) { final Context context = getActivity(); return PackageManager.PERMISSION_GRANTED == context.getPackageManager().checkPermission( permission, context.getPackageName()); } private void loadQuery(String query) { if ((mSearchQuery != null && !mSearchQuery.equals(query)) && !query.trim().isEmpty() || (!TextUtils.isEmpty(query) && !query.equals("nil"))) { if (NetworkUtil.isNetworkConnected(getActivity())) { mSearchQuery = query; searchCats(query); } else { ToastFactory.createWifiErrorToast(getActivity()).show(); } } } private void searchCats(String query) { mResultsAdapter.clear(); HeaderItem resultsHeader = new HeaderItem(0, getString(R.string.text_search_results)); mSearchObjectAdapter = new ArrayObjectAdapter(new CardPresenter()); ListRow listRow = new ListRow(resultsHeader, mSearchObjectAdapter); mResultsAdapter.add(listRow); mSearchObjectAdapter.clear(); searchCats(); } private void searchCats() { // Usually we'd load things from an API or database, for example here we just create // a list of cats from resources and return them back after passing them to the datamanager. // Obviously we wouldn't usually do this, but this is just for example and allows us // to still have an example unit test that doesn't require robolectric! Resources resources = getResources(); String[] names = resources.getStringArray(R.array.cat_names); String[] descriptions = resources.getStringArray(R.array.cat_descriptions); String[] images = resources.getStringArray(R.array.cat_images); List<Cat> cats = new ArrayList<>(); for (int i = 0; i < names.length; i++) { cats.add(new Cat(names[i], descriptions[i], images[i])); } mSearchContentPresenter.searchCats(cats); } private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() { @Override public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { // Handle item click } }; private OnItemViewSelectedListener mOnItemViewSelectedListener = new OnItemViewSelectedListener() { @Override public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { if (item instanceof Cat) { String backgroundUrl = ((Cat) item).imageUrl; if (backgroundUrl != null) startBackgroundTimer(URI.create(backgroundUrl)); } } }; @Override public void showCats(List<Cat> cats) { mSearchObjectAdapter.addAll(0, cats); } @Override public void showCatsError() { // show loading error state here String errorMessage = getString(R.string.error_message_generic); Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_SHORT).show(); } }