/* * Copyright 2016 Gleb Godonoga. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.andrada.sitracker.ui.fragment; import android.app.Activity; import android.app.LoaderManager; import android.content.Intent; import android.content.Loader; import android.net.Uri; import android.os.Bundle; import android.support.design.widget.Snackbar; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.style.ForegroundColorSpan; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.andrada.sitracker.Constants; import com.andrada.sitracker.R; import com.andrada.sitracker.analytics.AddAuthorEvent; import com.andrada.sitracker.analytics.SearchAuthorEvent; import com.andrada.sitracker.contracts.AppUriContract; import com.andrada.sitracker.db.beans.SearchedAuthor; import com.andrada.sitracker.events.AuthorAddedEvent; import com.andrada.sitracker.exceptions.SearchException; import com.andrada.sitracker.loader.AsyncTaskResult; import com.andrada.sitracker.loader.SamlibSearchLoader; import com.andrada.sitracker.tasks.AddAuthorTask; import com.andrada.sitracker.ui.BaseActivity; import com.andrada.sitracker.ui.fragment.adapters.SearchResultsAdapter; import com.andrada.sitracker.ui.widget.GridSpacingItemDecoration; import com.andrada.sitracker.analytics.AnalyticsManager; import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.EFragment; import org.androidannotations.annotations.UiThread; import org.androidannotations.annotations.ViewById; import org.jetbrains.annotations.NotNull; import java.util.List; import de.greenrobot.event.EventBus; import static com.andrada.sitracker.util.LogUtils.LOGD; import static com.andrada.sitracker.util.LogUtils.makeLogTag; @EFragment(R.layout.fragment_search) public class RemoteAuthorsFragment extends BaseFragment implements LoaderManager.LoaderCallbacks<AsyncTaskResult<List<SearchedAuthor>>>, SearchResultsAdapter.Callbacks { private static final String TAG = makeLogTag(RemoteAuthorsFragment.class); private static SearchResultsAdapter.Callbacks sDummyCallbacks = new SearchResultsAdapter.Callbacks() { @Override public void onAuthorSelected(SearchedAuthor author) { } }; @ViewById RecyclerView recyclerView; @ViewById ProgressBar loading; @ViewById TextView emptyText; @Bean SearchResultsAdapter adapter; private Bundle mArguments; private Uri mCurrentUri; public void onEvent(@NotNull AuthorAddedEvent event) { //Cancel any further delivery EventBus.getDefault().cancelEventDelivery(event); if (event.authorUrl != null) { //Find author with this url int pos = adapter.getItemPositionById(event.authorUrl); if (pos != -1) { AnalyticsManager.getInstance().logEvent(new AddAuthorEvent( adapter.getItem(pos).getAuthorName(), adapter.getItem(pos).getAuthorUrl())); adapter.getItem(pos).setAdded(true); recyclerView.getAdapter().notifyItemChanged(pos); } } loading.setVisibility(View.GONE); String message = event.message; if (getActivity() == null) { return; } SpannableStringBuilder snackbarText = new SpannableStringBuilder(); if (message.length() == 0) { //This is success snackbarText.append(getResources().getString(R.string.author_add_success_crouton_message)); } else { snackbarText.append(message); snackbarText.setSpan(new ForegroundColorSpan(0xFFFF0000), 0, snackbarText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } Snackbar.make(getView(), snackbarText, Snackbar.LENGTH_LONG).show(); } @AfterViews public void addDecorators() { final int displayCols = getResources().getInteger(R.integer.search_grid_columns); final float padding = getResources().getDimension(R.dimen.search_grid_padding); recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), displayCols)); recyclerView.addItemDecoration(new GridSpacingItemDecoration(displayCols, (int) padding, true)); } @Override public void onAuthorSelected(SearchedAuthor author) { final SearchedAuthor authorToAdd = author; if (author.isAdded()) { return; } new MaterialDialog.Builder(getActivity()) .title(getString(R.string.add_author_confirmation)) .positiveText(android.R.string.yes) .negativeText(android.R.string.no) .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(MaterialDialog dialog, DialogAction which) { loading.setVisibility(View.VISIBLE); new AddAuthorTask(getActivity()).execute(authorToAdd.getAuthorUrl()); } }) .build().show(); } @UiThread(delay = 100) void requestUpdateRecyclerView(List<SearchedAuthor> data) { updateRecyclerView(data); } public void requestQueryUpdate(String query, int searchType) { AnalyticsManager.getInstance().logEvent(new SearchAuthorEvent(query, String.valueOf(searchType))); //Test query for URL if (query.matches(Constants.SIMPLE_URL_REGEX) && query.startsWith(Constants.HTTP_PROTOCOL)) { //This looks like an url loading.setVisibility(View.VISIBLE); new AddAuthorTask(getActivity()).execute(query); } else { reloadFromArguments(BaseActivity.intentToFragmentArguments( new Intent(Intent.ACTION_SEARCH, AppUriContract.buildSamlibSearchUri(query, searchType)))); } } public void reloadFromArguments(Bundle arguments) { // Load new arguments if (arguments == null) { arguments = new Bundle(); } else { // since we might make changes, don't meddle with caller's copy arguments = (Bundle) arguments.clone(); } // save arguments so we can reuse it when reloading from content dataObserver events mArguments = arguments; LOGD(TAG, "SessionsFragment reloading from arguments: " + arguments); mCurrentUri = arguments.getParcelable("_uri"); LOGD(TAG, "SessionsFragment reloading, uri=" + mCurrentUri); reloadSearchData(); } private void reloadSearchData() { LOGD(TAG, "Reloading search data"); getLoaderManager().restartLoader(SamlibSearchLoader.SEARCH_TOKEN, mArguments, RemoteAuthorsFragment.this); emptyText.setVisibility(View.GONE); recyclerView.setVisibility(View.GONE); loading.setVisibility(View.VISIBLE); } private void updateRecyclerView(List<SearchedAuthor> data) { adapter.swapData(data); LOGD(TAG, "Data has " + data.size() + " items. Will now update recycler view."); int itemCount = data.size(); if (itemCount == 0) { showEmptyView(); } else { hideEmptyView(); } recyclerView.setHasFixedSize(true); recyclerView.setAdapter(adapter); } @Override public void onAttach(Activity context) { super.onAttach(context); if (adapter != null) { adapter.setCallbacks(this); if (adapter.getItemCount() > 0) { requestUpdateRecyclerView(adapter.getData()); } } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EventBus.getDefault().register(this); setRetainInstance(true); adapter.setCallbacks(this); //noinspection VariableNotUsedInsideIf if (mCurrentUri != null) { // Only if this is a config change should we initLoader(), to reconnect with an // existing loader. Otherwise, the loader will be init'd when reloadFromArguments // is called. getLoaderManager().initLoader(SamlibSearchLoader.SEARCH_TOKEN, null, RemoteAuthorsFragment.this); } } @Override public void onDestroy() { EventBus.getDefault().unregister(this); super.onDestroy(); } @Override public void onDetach() { super.onDetach(); adapter.setCallbacks(sDummyCallbacks); } private void hideEmptyView() { getBaseActivity().trySetToolbarScrollable(true); emptyText.setVisibility(View.GONE); loading.setVisibility(View.GONE); recyclerView.setVisibility(View.VISIBLE); } private void showEmptyView() { final String searchQuery = AppUriContract.isSearchUri(mCurrentUri) ? AppUriContract.getSearchQuery(mCurrentUri) : null; getBaseActivity().trySetToolbarScrollable(false); if (AppUriContract.isSearchUri(mCurrentUri) && (TextUtils.isEmpty(searchQuery) || "*".equals(searchQuery))) { // Empty search query (for example, user hasn't started to type the query yet), // so don't show an empty view. emptyText.setText(""); emptyText.setVisibility(View.VISIBLE); recyclerView.setVisibility(View.VISIBLE); loading.setVisibility(View.GONE); } else { // Showing authors as a result of search. If blank - show no resuls emptyText.setText(R.string.empty_search_results); emptyText.setVisibility(View.VISIBLE); recyclerView.setVisibility(View.VISIBLE); loading.setVisibility(View.GONE); } } @Override public Loader<AsyncTaskResult<List<SearchedAuthor>>> onCreateLoader(int id, Bundle data) { LOGD(TAG, "onCreateLoader, id=" + id + ", data=" + data); final Intent intent = BaseActivity.fragmentArgumentsToIntent(data); Uri searchUri = intent.getData(); Loader<AsyncTaskResult<List<SearchedAuthor>>> loader = null; if (id == SamlibSearchLoader.SEARCH_TOKEN) { LOGD(TAG, "Creating search loader for " + searchUri); loader = new SamlibSearchLoader(getActivity(), searchUri); } return loader; } @SuppressWarnings("ThrowableResultOfMethodCallIgnored") @Override public void onLoadFinished(Loader<AsyncTaskResult<List<SearchedAuthor>>> loader, AsyncTaskResult<List<SearchedAuthor>> data) { if (getActivity() == null) { return; } int token = loader.getId(); LOGD(TAG, "Loader finished: search"); if (token == SamlibSearchLoader.SEARCH_TOKEN) { if (data.getError() instanceof SearchException) { int errorMsg; switch (((SearchException) data.getError()).getError()) { case SAMLIB_BUSY: errorMsg = R.string.cannot_search_busy; break; case NETWORK_ERROR: errorMsg = R.string.cannot_search_network; break; case INTERNAL_ERROR: errorMsg = R.string.cannot_search_internal; break; default: errorMsg = R.string.cannot_search_unknown; break; } SpannableStringBuilder snackbarText = new SpannableStringBuilder(); snackbarText.append(getString(errorMsg)); snackbarText.setSpan(new ForegroundColorSpan(0xFFFF0000), 0, snackbarText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); Snackbar.make(getView(), snackbarText, Snackbar.LENGTH_SHORT).show(); } updateRecyclerView(data.getResult()); } } @Override public void onLoaderReset(Loader<AsyncTaskResult<List<SearchedAuthor>>> listLoader) { } }