package com.etiennelawlor.loop.fragments; import android.app.Activity; import android.app.Dialog; import android.app.SearchManager; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Typeface; import android.os.Bundle; import android.speech.RecognizerIntent; import android.support.annotation.NonNull; import android.support.v4.app.ActivityOptionsCompat; import android.support.v4.util.Pair; import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; 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.ArrayAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; import com.etiennelawlor.loop.R; import com.etiennelawlor.loop.activities.SearchableActivity; import com.etiennelawlor.loop.activities.VideoDetailsActivity; import com.etiennelawlor.loop.adapters.VideosAdapter; import com.etiennelawlor.loop.bus.RxBus; import com.etiennelawlor.loop.bus.events.FilterClickedEvent; import com.etiennelawlor.loop.bus.events.SearchPerformedEvent; import com.etiennelawlor.loop.bus.events.ShowSearchSuggestionsEvent; import com.etiennelawlor.loop.models.AccessToken; import com.etiennelawlor.loop.network.ServiceGenerator; import com.etiennelawlor.loop.network.VimeoService; import com.etiennelawlor.loop.network.interceptors.AuthorizedNetworkInterceptor; import com.etiennelawlor.loop.network.models.response.Video; import com.etiennelawlor.loop.network.models.response.VideosEnvelope; import com.etiennelawlor.loop.prefs.LoopPrefs; import com.etiennelawlor.loop.realm.RealmUtility; import com.etiennelawlor.loop.ui.LoadingImageView; import com.etiennelawlor.loop.ui.MaterialSearchView; import com.etiennelawlor.loop.utilities.FontCache; import com.etiennelawlor.loop.utilities.NetworkLogUtility; import com.etiennelawlor.loop.utilities.NetworkUtility; import java.util.ArrayList; import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.Unbinder; import jp.wasabeef.recyclerview.animators.SlideInUpAnimator; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action1; import rx.subscriptions.CompositeSubscription; /** * Created by etiennelawlor on 5/23/15. */ public class SearchableFragment extends BaseFragment implements VideosAdapter.OnItemClickListener, VideosAdapter.OnReloadClickListener { // region Constants public static final int PAGE_SIZE = 30; // endregion // region Views @BindView(R.id.rv) RecyclerView recyclerView; @BindView(android.R.id.empty) View emptyView; @BindView(R.id.empty_tv) TextView emptyTextView; @BindView(R.id.iv) ImageView imageView; @BindView(R.id.loading_iv) LoadingImageView loadingImageView; @BindView(R.id.error_ll) LinearLayout errorLinearLayout; @BindView(R.id.error_tv) TextView errorTextView; @BindView(R.id.material_sv) MaterialSearchView materialSearchView; // @Bind(R.id.toolbar) // Toolbar toolbar; // endregion // region Member Variables private boolean isLastPage = false; private int currentPage = 1; private int selectedSortByKey = 0; private int selectedSortOrderKey = 1; private boolean isLoading = false; private String sortByValue = "relevant"; private String sortOrderValue = "desc"; private String filter = "CC"; private VideosAdapter videosAdapter; private String query; private Typeface font; private Unbinder unbinder; private LinearLayoutManager layoutManager; private VimeoService vimeoService; private CompositeSubscription compositeSubscription; // endregion // region Listeners private RecyclerView.OnScrollListener recyclerViewOnScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); int visibleItemCount = layoutManager.getChildCount(); int totalItemCount = layoutManager.getItemCount(); int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition(); if (!isLoading && !isLastPage) { if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount) { loadMoreItems(); } } } }; @OnClick(R.id.reload_btn) public void onReloadButtonClicked() { errorLinearLayout.setVisibility(View.GONE); loadingImageView.setVisibility(View.VISIBLE); materialSearchView.disableFilter(); Call findVideosCall = vimeoService.findVideos(query, sortByValue, sortOrderValue, currentPage, PAGE_SIZE, filter); calls.add(findVideosCall); findVideosCall.enqueue(findVideosFirstFetchCallback); } // endregion // region Callbacks private Callback<VideosEnvelope> findVideosFirstFetchCallback = new Callback<VideosEnvelope>() { @Override public void onResponse(Call<VideosEnvelope> call, Response<VideosEnvelope> response) { loadingImageView.setVisibility(View.GONE); isLoading = false; if (!response.isSuccessful()) { int responseCode = response.code(); if(responseCode == 504) { // 504 Unsatisfiable Request (only-if-cached) errorTextView.setText("Can't load data.\nCheck your network connection."); errorLinearLayout.setVisibility(View.VISIBLE); } return; } VideosEnvelope videosEnvelope = response.body(); if (videosEnvelope != null) { List<Video> videos = videosEnvelope.getVideos(); if (videos != null) { if(videos.size()>0) videosAdapter.addAll(videos); if(videos.size() >= PAGE_SIZE){ videosAdapter.addFooter(); } else { isLastPage = true; } } } materialSearchView.enableFilter(); if (videosAdapter.isEmpty()) { emptyTextView.setText("There are no videos matching your search"); // Drawable drawable = ContextCompat.getDrawable(getContext(), R.drawable.ic_watch_later_large); // DrawableCompat.setTint(drawable, ContextCompat.getColor(getActivity(), R.color.grey_500)); // emptyTextView.setCompoundDrawablesWithIntrinsicBounds(null, drawable, null, null); imageView.setImageResource(R.drawable.ic_watch_now_large); emptyView.setVisibility(View.VISIBLE); } } @Override public void onFailure(Call<VideosEnvelope> call, Throwable t) { NetworkLogUtility.logFailure(call, t); if (!call.isCanceled()){ isLoading = false; loadingImageView.setVisibility(View.GONE); if(NetworkUtility.isKnownException(t)){ errorTextView.setText("Can't load data.\nCheck your network connection."); errorLinearLayout.setVisibility(View.VISIBLE); } } } }; private Callback<VideosEnvelope> findVideosNextFetchCallback = new Callback<VideosEnvelope>() { @Override public void onResponse(Call<VideosEnvelope> call, Response<VideosEnvelope> response) { videosAdapter.removeFooter(); isLoading = false; if (!response.isSuccessful()) { int responseCode = response.code(); switch (responseCode){ case 504: // 504 Unsatisfiable Request (only-if-cached) break; case 400: isLastPage = true; break; } return; } VideosEnvelope videosEnvelope = response.body(); if (videosEnvelope != null) { List<Video> videos = videosEnvelope.getVideos(); if (videos != null) { if(videos.size()>0) videosAdapter.addAll(videos); if(videos.size() >= PAGE_SIZE){ videosAdapter.addFooter(); } else { isLastPage = true; } } } } @Override public void onFailure(Call<VideosEnvelope> call, Throwable t) { NetworkLogUtility.logFailure(call, t); if (!call.isCanceled()){ if(NetworkUtility.isKnownException(t)){ videosAdapter.updateFooter(VideosAdapter.FooterType.ERROR); } } } }; // endregion // region Constructors public SearchableFragment() { } // endregion // region Factory Methods public static SearchableFragment newInstance() { return new SearchableFragment(); } public static SearchableFragment newInstance(Bundle extras) { SearchableFragment fragment = new SearchableFragment(); fragment.setArguments(extras); return fragment; } // endregion // region Lifecycle Methods @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Retain this fragment across configuration changes. setRetainInstance(true); if(getArguments() != null){ query = getArguments().getString(SearchManager.QUERY); RealmUtility.saveQuery(query); // performSearch(query); } AccessToken token = LoopPrefs.getAccessToken(getActivity()); vimeoService = ServiceGenerator.createService( VimeoService.class, VimeoService.BASE_URL, new AuthorizedNetworkInterceptor(token)); setHasOptionsMenu(true); font = FontCache.getTypeface("Ubuntu-Medium.ttf", getContext()); compositeSubscription = new CompositeSubscription(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_searchable, container, false); unbinder = ButterKnife.bind(this, rootView); return rootView; } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // ((AppCompatActivity) getActivity()).setSupportActionBar(mToolbar); // // final ActionBar ab = ((AppCompatActivity) getActivity()).getSupportActionBar(); // ab.setDisplayHomeAsUpEnabled(true); // ab.setTitle(""); setupSearchView(); setUpRxBusSubscription(); // mSearchViewWidget.setQuery(mQuery, false); // // mSearchViewWidget.setOnQueryTextListener(new SearchViewWidget2.OnQueryTextListener() { // // @Override // public boolean onQueryTextSubmit(String query) { // Intent intent = new Intent(mSearchViewWidget.getContext(), SearchableActivity.class); // intent.setAction(Intent.ACTION_SEARCH); // intent.putExtra(SearchManager.QUERY, query); // mSearchViewWidget.getContext().startActivity(intent); // // return false; // } // // @Override // public boolean onQueryTextChange(String newText) { // return false; // } // }); // // mSearchViewWidget.setOnSearchViewListener(new SearchViewWidget2.SearchViewListener() { // // @Override // public void onSearchViewShown() { // } // // @Override // public void onSearchViewClosed() { // } // }); // // List<SearchViewItem> mSuggestionsList = new ArrayList<>(); // mSuggestionsList.add(new SearchViewItem(R.drawable.ic_search_black_24dp, "Wi-Fi")); // mSuggestionsList.add(new SearchViewItem(R.drawable.ic_search_black_24dp, "Bluetooth")); // mSuggestionsList.add(new SearchViewItem(R.drawable.ic_search_black_24dp, "GPS")); // mSuggestionsList.add(new SearchViewItem(R.drawable.ic_search_black_24dp, "Ad-Hoc")); // mSuggestionsList.add(new SearchViewItem(R.drawable.ic_search_black_24dp, "Google")); // mSuggestionsList.add(new SearchViewItem(R.drawable.ic_search_black_24dp, "Android")); // mSuggestionsList.add(new SearchViewItem(R.drawable.ic_search_black_24dp, "Piconet")); // mSuggestionsList.add(new SearchViewItem(R.drawable.ic_search_black_24dp, "Scatternet")); // // List<SearchViewItem> mResultsList = new ArrayList<>(); // // choose true for Light Theme, false for Dark Theme. // SearchViewAdapter mSearchViewAdapter = new SearchViewAdapter(getActivity(), mResultsList, mSuggestionsList, true); // mSearchViewAdapter.setOnItemClickListener(new SearchViewAdapter.OnItemClickListener() { // @Override // public void onItemClick(View view, int position) { // TextView mText = (TextView) view.findViewById(R.id.textView_result); // CharSequence text = "Hello toast!"; //// int duration = Toast.LENGTH_SHORT; //// Toast toast = Toast.makeText(getApplicationContext(), text, duration); //// toast.show(); // // } // }); // mSearchViewWidget.setAdapter(mSearchViewAdapter); layoutManager = new LinearLayoutManager(getActivity()); recyclerView.setLayoutManager(layoutManager); videosAdapter = new VideosAdapter(); videosAdapter.setOnItemClickListener(this); videosAdapter.setOnReloadClickListener(this); recyclerView.setItemAnimator(new SlideInUpAnimator()); recyclerView.setAdapter(videosAdapter); // Pagination recyclerView.addOnScrollListener(recyclerViewOnScrollListener); Call findVideosCall = vimeoService.findVideos(query, sortByValue, sortOrderValue, currentPage, PAGE_SIZE, filter); calls.add(findVideosCall); findVideosCall.enqueue(findVideosFirstFetchCallback); } @Override public void onResume() { super.onResume(); setupSearchView(); } @Override public void onDestroyView() { super.onDestroyView(); removeListeners(); unbinder.unbind(); } @Override public void onDestroy() { super.onDestroy(); compositeSubscription.unsubscribe(); } // endregion @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); // inflater.inflate(R.menu.searchable_menu, menu); // Get the SearchView and set the searchable configuration // SearchManager searchManager = (SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE); // SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView(); // Assumes current activity is the searchable activity // searchView.setSearchableInfo(searchManager.getSearchableInfo(getActivity().getComponentName())); // searchView.setQueryRefinementEnabled(true); // searchView.onActionViewExpanded(); // searchView.setIconified(false); // searchView.setQuery(mQuery, false); // searchView.clearFocus(); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { default: // do nothing break; } return super.onOptionsItemSelected(item); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == MaterialSearchView.REQUEST_VOICE && resultCode == Activity.RESULT_OK) { ArrayList<String> matches = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); if (matches != null && matches.size() > 0) { String searchWrd = matches.get(0); if (!TextUtils.isEmpty(searchWrd)) { // mMaterialSearchView.setQuery(searchWrd); launchSearchActivity(searchWrd); } } return; } super.onActivityResult(requestCode, resultCode, data); } // region VideosAdapter.OnItemClickListener Methods @Override public void onItemClick(int position, View view) { Video video = videosAdapter.getItem(position); if (video != null) { Intent intent = new Intent(getActivity(), VideoDetailsActivity.class); Bundle bundle = new Bundle(); bundle.putParcelable(LikedVideosFragment.KEY_VIDEO, video); intent.putExtras(bundle); Pair<View, String> p1 = Pair.create(view.findViewById(R.id.video_thumbnail_iv), "videoTransition"); // Pair<View, String> p2 = Pair.create((View) view.findViewById(R.id.title_tv), "titleTransition"); // Pair<View, String> p3 = Pair.create((View) view.findViewById(R.id.subtitle_tv), "subtitleTransition"); // Pair<View, String> p4 = Pair.create((View)view.findViewById(R.id.uploaded_tv), "uploadedTransition"); // ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), // p1, p2, p3); ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), p1); // ActivityCompat.startActivity(getActivity(), intent, options.toBundle()); startActivity(intent); } } // endregion // region VideosAdapter.OnReloadClickListener Methods @Override public void onReloadClick() { videosAdapter.updateFooter(VideosAdapter.FooterType.LOAD_MORE); Call findVideosCall = vimeoService.findVideos(query, sortByValue, sortOrderValue, currentPage, PAGE_SIZE, filter); calls.add(findVideosCall); findVideosCall.enqueue(findVideosNextFetchCallback); } // endregion // region Helper Methods private void loadMoreItems() { isLoading = true; currentPage += 1; Call findVideosCall = vimeoService.findVideos(query, sortByValue, sortOrderValue, currentPage, PAGE_SIZE, filter); calls.add(findVideosCall); findVideosCall.enqueue(findVideosNextFetchCallback); } private void showSortDialog() { LayoutInflater li = LayoutInflater.from(getActivity()); View promptsView = li.inflate(R.layout.sort_dialog, null); final Spinner sortBySpinner = (Spinner) promptsView.findViewById(R.id.sort_by_s); final Spinner sortOrderSpinner = (Spinner) promptsView.findViewById(R.id.sort_order_s); sortBySpinner.setAdapter(new ArrayAdapter<String>(getActivity(), R.layout.sort_row, R.id.sort_tv, getResources().getStringArray(R.array.videos_sort_by_keys)){ @NonNull @Override public View getView(int position, View convertView, ViewGroup parent) { View v = super.getView(position, convertView, parent); return v; } @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { View v = super.getView(position, convertView, parent); return v; } }); sortOrderSpinner.setAdapter(new ArrayAdapter<String>(getActivity(), R.layout.sort_row, R.id.sort_tv, getResources().getStringArray(R.array.videos_sort_order_keys)){ @NonNull @Override public View getView(int position, View convertView, ViewGroup parent) { View v = super.getView(position, convertView, parent); return v; } @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { View v = super.getView(position, convertView, parent); return v; } }); sortBySpinner.setSelection(selectedSortByKey); sortOrderSpinner.setSelection(selectedSortOrderKey); AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity(), R.style.DialogTheme); alertDialogBuilder.setView(promptsView); alertDialogBuilder.setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { selectedSortByKey = sortBySpinner.getSelectedItemPosition(); selectedSortOrderKey = sortOrderSpinner.getSelectedItemPosition(); String[] sortByValues = getResources().getStringArray(R.array.videos_sort_by_values); sortByValue = sortByValues[selectedSortByKey]; String[] sortOrderValues = getResources().getStringArray(R.array.videos_sort_order_values); sortOrderValue = sortOrderValues[selectedSortOrderKey]; videosAdapter.clear(); loadingImageView.setVisibility(View.VISIBLE); currentPage = 1; materialSearchView.disableFilter(); Call findVideosCall = vimeoService.findVideos(query, sortByValue, sortOrderValue, currentPage, PAGE_SIZE, filter); calls.add(findVideosCall); findVideosCall.enqueue(findVideosFirstFetchCallback); dialog.dismiss(); } }); alertDialogBuilder.setNegativeButton(getString(android.R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); AlertDialog alertDialog = alertDialogBuilder.show(); Button btnPositive = alertDialog.getButton(Dialog.BUTTON_POSITIVE); btnPositive.setTypeface(font); Button btnNegative = alertDialog.getButton(Dialog.BUTTON_NEGATIVE); btnNegative.setTypeface(font); } private void launchSearchActivity(String query){ Intent intent = new Intent(getContext(), SearchableActivity.class); intent.setAction(Intent.ACTION_SEARCH); Bundle bundle = new Bundle(); bundle.putString(SearchManager.QUERY, query); intent.putExtras(bundle); getContext().startActivity(intent); getActivity().overridePendingTransition(0, 0); } private void setupSearchView(){ materialSearchView.setQuery(query); } private void removeListeners(){ recyclerView.removeOnScrollListener(recyclerViewOnScrollListener); } private void setUpRxBusSubscription(){ Subscription rxBusSubscription = RxBus.getInstance().toObserverable() .observeOn(AndroidSchedulers.mainThread()) // UI Thread .subscribe(new Action1<Object>() { @Override public void call(Object event) { // if (event == null || !isResumed()) { // return; // } if (event == null) { return; } if(event instanceof FilterClickedEvent) { showSortDialog(); } else if(event instanceof SearchPerformedEvent) { String query = ((SearchPerformedEvent)event).getQuery(); if (!TextUtils.isEmpty(query)) { launchSearchActivity(query); } } else if(event instanceof ShowSearchSuggestionsEvent) { String query = ((ShowSearchSuggestionsEvent)event).getQuery(); materialSearchView.addSuggestions(RealmUtility.getSuggestions(query)); } } }); compositeSubscription.add(rxBusSubscription); } // endregion }