/**
* *******************************************************************************
* Copyright 2015 Tom Praschan
* <p/>
* 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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 de.tap.easy_xkcd.Activities;
import android.app.ProgressDialog;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.CardView;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.Html;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.InputMethodManager;
import android.widget.ImageView;
import android.widget.SearchView;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.kogitune.activity_transition.ActivityTransitionLauncher;
import com.tap.xkcd_reader.R;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.PatternSyntaxException;
import butterknife.Bind;
import butterknife.ButterKnife;
import de.tap.easy_xkcd.database.DatabaseManager;
import de.tap.easy_xkcd.database.RealmComic;
import de.tap.easy_xkcd.fragments.comics.ComicBrowserFragment;
import de.tap.easy_xkcd.fragments.comics.OfflineFragment;
import io.realm.Case;
import io.realm.Realm;
import io.realm.RealmResults;
import jp.wasabeef.recyclerview.adapters.SlideInBottomAnimationAdapter;
public class SearchResultsActivity extends BaseActivity {
private ArrayList<Integer> resultsTitle = new ArrayList<>();
private ArrayList<Integer> resultsTranscript = new ArrayList<>();
@Bind(R.id.rv)
RecyclerView rv;
@Bind(R.id.toolbar)
Toolbar toolbar;
private searchTask task;
private ProgressDialog mProgress;
private String query;
public static boolean isOpen;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search_results);
ButterKnife.bind(this);
DatabaseManager databaseManager = new DatabaseManager(this);
setupToolbar(toolbar);
LinearLayoutManager llm = new LinearLayoutManager(this);
rv.setLayoutManager(llm);
rv.setHasFixedSize(true);
Intent intent = getIntent();
if (savedInstanceState == null)
query = intent.getStringExtra(SearchManager.QUERY);
else
query = savedInstanceState.getString("query");
getSupportActionBar().setTitle(getResources().getString(R.string.title_activity_search_results) + " " + query);
mProgress = ProgressDialog.show(this, "", getResources().getString(R.string.loading_results), true);
if (savedInstanceState == null)
databaseManager.new updateComicDatabase(this, null, prefHelper).execute();
else {
task = new searchTask();
task.execute(query);
}
}
/**
* Called by the databaseManager in order to start the search after the database has been updated
*/
public void updateDatabasePostExecute() {
task = new searchTask();
task.execute(query);
}
/**
* Checks if the query is integer or text and invokes the according search algorithm
*
* @param query the query entered by the user
* @return false if there haven't been any search results
*/
private boolean performSearch(String query) {
if (checkInteger(query)) {
return getComicByNumber(Integer.parseInt(query));
} else {
return searchComicTitleOrTranscript(query);
}
}
/**
* Shows the comic in the Comic Browser
*
* @param number the comic to be shown
* @return always true to let the activity know that it doesn't have to do any more work
*/
private boolean getComicByNumber(int number) {
Intent intent = new Intent("de.tap.easy_xkcd.ACTION_COMIC");
if ((number > prefHelper.getNewest() && number > prefHelper.getHighestOffline()) | number < 1) //check if the number is a valid comic
intent.putExtra("number", prefHelper.getNewest());
else
intent.putExtra("number", number);
mProgress.dismiss();
startActivity(intent);
if (task != null)
task.cancel(true);
return true;
}
/**
* Searches the database for the comics containing the query
* @return false if there haven't been any search results
*/
private boolean searchComicTitleOrTranscript(String query) {
Realm realm = Realm.getInstance(SearchResultsActivity.this);
query = query.trim();
resultsTitle.clear();
resultsTranscript.clear();
RealmResults<RealmComic> title = realm.where(RealmComic.class).contains("title", query, Case.INSENSITIVE).findAll();
for (RealmComic comic : title)
resultsTitle.add(comic.getComicNumber());
RealmResults<RealmComic> trans = realm.where(RealmComic.class).contains("transcript", query, Case.INSENSITIVE).not().contains("title", query, Case.INSENSITIVE).findAll();
for (RealmComic comic : trans)
this.resultsTranscript.add(comic.getComicNumber());
realm.close();
return (resultsTranscript.size() + resultsTitle.size() == 0);
}
private class searchTask extends AsyncTask<String, Void, Void> {
private Boolean searchSuccessful;
@Override
protected Void doInBackground(String... params) {
query = params[0];
searchSuccessful = performSearch(params[0]);
return null;
}
@Override
protected void onPostExecute(Void dummy) {
if (!searchSuccessful) {
RVAdapter adapter = new RVAdapter();
SlideInBottomAnimationAdapter slideAdapter = new SlideInBottomAnimationAdapter(adapter);
slideAdapter.setInterpolator(new DecelerateInterpolator());
rv.setAdapter(slideAdapter);
assert getSupportActionBar() != null;
getSupportActionBar().setTitle(getResources().getString(R.string.title_activity_search_results) + " " + query);
} else
Toast.makeText(SearchResultsActivity.this, R.string.search_error, Toast.LENGTH_SHORT).show();
mProgress.dismiss();
}
}
public class RVAdapter extends RecyclerView.Adapter<RVAdapter.ComicViewHolder> {
private Realm realm;
public RVAdapter() {
realm = Realm.getInstance(SearchResultsActivity.this);
}
@Override
public int getItemCount() {
return resultsTitle.size() + resultsTranscript.size();
}
@Override
public ComicViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.search_result, viewGroup, false);
v.setOnClickListener(new CustomOnClickListener());
return new ComicViewHolder(v);
}
@Override
public void onBindViewHolder(final ComicViewHolder comicViewHolder, int i) {
RealmComic comic;
if (i < resultsTitle.size())
comic = realm.where(RealmComic.class).equalTo("comicNumber", resultsTitle.get(i)).findFirst();
else
comic = realm.where(RealmComic.class).equalTo("comicNumber", resultsTranscript.get(i - resultsTitle.size())).findFirst();
int number = comic.getComicNumber();
String title = comic.getTitle();
String preview = getPreview(query, comic.getTranscript());
String url = comic.getUrl();
comicViewHolder.comicTitle.setText(title);
if (i < resultsTitle.size())
comicViewHolder.comicInfo.setText(String.valueOf(number));
else
comicViewHolder.comicInfo.setText(Html.fromHtml(preview));
//Load the thumbnail
if (!MainActivity.fullOffline) {
Glide.with(SearchResultsActivity.this)
.load(url)
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.into(comicViewHolder.thumbnail);
} else {
try {
FileInputStream fis = openFileInput(String.valueOf(number));
Bitmap mBitmap = BitmapFactory.decodeStream(fis);
fis.close();
comicViewHolder.thumbnail.setImageBitmap(mBitmap);
} catch (Exception e) {
Log.e("Error", "loading from internal storage failed");
try {
File sdCard = prefHelper.getOfflinePath();
File dir = new File(sdCard.getAbsolutePath() + "/easy xkcd");
File file = new File(dir, String.valueOf(number) + ".png");
Glide.with(getApplicationContext())
.load(file)
.asBitmap()
.into(comicViewHolder.thumbnail);
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
if (themePrefs.invertColors(false))
comicViewHolder.thumbnail.setColorFilter(themePrefs.getNegativeColorFilter());
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
public class ComicViewHolder extends RecyclerView.ViewHolder {
CardView cv;
TextView comicTitle;
TextView comicInfo;
ImageView thumbnail;
ComicViewHolder(View itemView) {
super(itemView);
cv = (CardView) itemView.findViewById(R.id.cv);
if (themePrefs.nightThemeEnabled())
cv.setCardBackgroundColor(ContextCompat.getColor(SearchResultsActivity.this, R.color.background_material_dark));
comicTitle = (TextView) itemView.findViewById(R.id.comic_title);
comicInfo = (TextView) itemView.findViewById(R.id.comic_info);
thumbnail = (ImageView) itemView.findViewById(R.id.thumbnail);
}
}
}
/**
* Creates a preview for the transcript of comics that contain the query
* @param query the users's query
* @param transcript the comic's transcript
* @return a short preview of the transcript with the query highlighted
*/
private String getPreview(String query, String transcript) {
try {
String firstWord = query.split(" ")[0].toLowerCase();
transcript = transcript.replace(".", ". ").replace("?", "? ").replace("]]", " ").replace("[[", " ").replace("{{", " ").replace("}}", " ");
ArrayList<String> words = new ArrayList<>(Arrays.asList(transcript.toLowerCase().split(" ")));
int i = 0;
boolean found = false;
while (!found && i < words.size()) {
if (query.length() < 5)
found = words.get(i).matches(".*\\b" + firstWord + "\\b.*");
else
found = words.get(i).contains(firstWord);
if (!found) i++;
}
int start = i - 6;
int end = i + 6;
if (i < 6) start = 0;
if (words.size() - i < 6) end = words.size();
StringBuilder sb = new StringBuilder();
for (String s : words.subList(start, end)) {
sb.append(s);
sb.append(" ");
}
String s = sb.toString();
return "..." + s.replace(query, "<b>" + query + "</b>") + "...";
} catch (PatternSyntaxException e) {
e.printStackTrace();
return " ";
}
}
class CustomOnClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
ImageView imageView = (ImageView) v.findViewById(R.id.thumbnail);
if (imageView.getDrawable() != null) {
int pos = rv.getChildAdapterPosition(v);
Intent intent = new Intent("de.tap.easy_xkcd.ACTION_COMIC");
if (pos < resultsTitle.size())
intent.putExtra("number", resultsTitle.get(pos));
else
intent.putExtra("number", resultsTranscript.get(pos - resultsTitle.size()));
Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
if (!prefHelper.fullOfflineEnabled())
ComicBrowserFragment.fromSearch = true;
else
OfflineFragment.fromSearch = true;
ActivityTransitionLauncher.with(SearchResultsActivity.this).from(v.findViewById(R.id.thumbnail)).image(bitmap).launch(intent);
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_search_results, menu);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
final SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setIconifiedByDefault(false);
MenuItem searchMenuItem = menu.findItem(R.id.action_search);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
if (task != null) {
task.cancel(true);
}
task = new searchTask();
task.execute(newText);
query = newText;
return false;
}
});
MenuItemCompat.setOnActionExpandListener(searchMenuItem, new MenuItemCompat.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
View view = getCurrentFocus();
if (view != null) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(view, 0);
}
searchView.requestFocus();
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
View view = getCurrentFocus();
if (view != null) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
return true;
}
});
return true;
}
@Override
protected void onPause() {
super.onPause();
if (mProgress != null)
mProgress.dismiss();
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private boolean checkInteger(String s) {
boolean isInteger = true;
try {
Integer.parseInt(s.trim());
} catch (Exception e) {
isInteger = false;
}
return isInteger;
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putString("query", query);
super.onSaveInstanceState(savedInstanceState);
}
@Override
public void onBackPressed() {
MainActivity.fromSearch = true;
super.onBackPressed();
}
@Override
protected void onStart() {
super.onStart();
isOpen = true;
}
@Override
protected void onDestroy() {
super.onDestroy();
isOpen = false;
}
}