Do wikivoyage search in background

This commit is contained in:
Alexander Sytnyk 2018-03-28 18:27:20 +03:00
parent 3c208a8797
commit a55b6d085e
4 changed files with 194 additions and 53 deletions

View file

@ -2,7 +2,6 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/ctx_menu_info_view_bg"
@ -22,34 +21,7 @@
app:contentInsetLeft="54dp"
app:contentInsetStart="54dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical">
<EditText
android:id="@+id/search_edit_text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@null"
android:gravity="center_vertical"
android:hint="@string/shared_string_search"
android:imeOptions="actionSearch"
android:inputType="text"
android:maxLines="1"
android:textColor="?attr/searchbar_text"
android:textColorHint="?attr/searchbar_text_hint"/>
<ImageButton
android:id="@+id/search_button"
style="@style/Widget.AppCompat.ActionButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/shared_string_search"
tools:src="@drawable/ic_action_search_dark"/>
</LinearLayout>
<include layout="@layout/search_text_layout"/>
</android.support.v7.widget.Toolbar>

View file

@ -1,5 +1,6 @@
package net.osmand.plus.wikivoyage.search;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
@ -48,8 +49,12 @@ public class SearchRecyclerViewAdapter extends RecyclerView.Adapter<SearchRecycl
return items.get(pos);
}
public void setItems(List<SearchResult> items) {
this.items = items;
public void setItems(@Nullable List<SearchResult> items) {
if (items == null) {
this.items.clear();
} else {
this.items = items;
}
notifyDataSetChanged();
}

View file

@ -0,0 +1,103 @@
package net.osmand.plus.wikivoyage.search;
import android.os.AsyncTask;
import android.support.annotation.Nullable;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.wikivoyage.data.SearchResult;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class WikivoyageSearchCore {
private static final int TIMEOUT_BETWEEN_CHARS = 700;
private static final int SLEEP_TIME = 50;
private LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
private ThreadPoolExecutor singleThreadExecutor =
new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS, workQueue);
private AsyncTask<Void, Void, List<SearchResult>> currentTask;
private OsmandApplication application;
private Set<SearchListener> listeners = new HashSet<>();
WikivoyageSearchCore(OsmandApplication application) {
this.application = application;
}
public void registerListener(SearchListener listener) {
listeners.add(listener);
}
public void unregisterListener(SearchListener listener) {
listeners.remove(listener);
}
public void cancelSearch() {
workQueue.clear();
if (currentTask != null) {
currentTask.cancel(true);
}
}
public void search(String query) {
cancelSearch();
currentTask = new SearchAsyncTask(query);
currentTask.executeOnExecutor(singleThreadExecutor);
}
public interface SearchListener {
void onSearchStarted();
void onSearchFinished(@Nullable List<SearchResult> results, boolean lastTask, boolean cancelled);
}
private class SearchAsyncTask extends AsyncTask<Void, Void, List<SearchResult>> {
private String query;
SearchAsyncTask(String query) {
this.query = query;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
for (SearchListener listener : listeners) {
listener.onSearchStarted();
}
}
@Override
protected List<SearchResult> doInBackground(Void... voids) {
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime <= TIMEOUT_BETWEEN_CHARS) {
if (isCancelled()) {
return null;
}
try {
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return application.getWikivoyageDbHelper().search(query);
}
@Override
protected void onPostExecute(List<SearchResult> results) {
super.onPostExecute(results);
boolean cancelled = isCancelled();
for (SearchListener listener : listeners) {
listener.onSearchFinished(results, workQueue.isEmpty(), cancelled);
}
}
}
}

View file

@ -6,35 +6,47 @@ import android.support.v4.app.FragmentManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.KeyEvent;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.ProgressBar;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.base.BaseOsmAndDialogFragment;
import net.osmand.plus.wikivoyage.WikivoyageArticleDialogFragment;
import net.osmand.plus.wikivoyage.data.WikivoyageDbHelper;
import net.osmand.plus.wikivoyage.data.SearchResult;
public class WikivoyageSearchDialogFragment extends BaseOsmAndDialogFragment {
import java.util.List;
public class WikivoyageSearchDialogFragment extends BaseOsmAndDialogFragment implements WikivoyageSearchCore.SearchListener {
public static final String TAG = "WikivoyageSearchDialogFragment";
private WikivoyageDbHelper dbHelper;
private WikivoyageSearchCore searchCore;
private String searchQuery = "";
private SearchRecyclerViewAdapter adapter;
private EditText searchEt;
private ImageButton clearIb;
private ProgressBar progressBar;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
dbHelper = getMyApplication().getWikivoyageDbHelper();
final OsmandApplication app = getMyApplication();
searchCore = new WikivoyageSearchCore(app);
final boolean nightMode = !app.getSettings().isLightContent();
final int themeRes = nightMode ? R.style.OsmandDarkTheme : R.style.OsmandLightTheme;
final View mainView = inflater.inflate(R.layout.fragment_wikivoyage_search_dialog, container);
final View mainView = LayoutInflater.from(new ContextThemeWrapper(app, themeRes))
.inflate(R.layout.fragment_wikivoyage_search_dialog, container, false);
Toolbar toolbar = (Toolbar) mainView.findViewById(R.id.toolbar);
toolbar.setNavigationIcon(getContentIcon(R.drawable.ic_arrow_back));
@ -46,24 +58,40 @@ public class WikivoyageSearchDialogFragment extends BaseOsmAndDialogFragment {
}
});
searchEt = (EditText) toolbar.findViewById(R.id.search_edit_text);
searchEt.setOnEditorActionListener(new TextView.OnEditorActionListener() {
searchEt = (EditText) toolbar.findViewById(R.id.searchEditText);
searchEt.setHint(R.string.shared_string_search);
searchEt.addTextChangedListener(new TextWatcher() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
runSearch();
return true;
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
String newQuery = s.toString();
if (newQuery.isEmpty()) {
searchCore.cancelSearch();
adapter.setItems(null);
} else if (!searchQuery.equalsIgnoreCase(newQuery)) {
searchQuery = newQuery;
searchCore.search(searchQuery);
}
return false;
}
});
ImageButton searchBtn = (ImageButton) mainView.findViewById(R.id.search_button);
searchBtn.setImageDrawable(getContentIcon(R.drawable.ic_action_search_dark));
searchBtn.setOnClickListener(new View.OnClickListener() {
progressBar = (ProgressBar) toolbar.findViewById(R.id.searchProgressBar);
clearIb = (ImageButton) toolbar.findViewById(R.id.clearButton);
clearIb.setImageDrawable(getContentIcon(R.drawable.ic_action_remove_dark));
clearIb.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
runSearch();
public void onClick(View v) {
searchEt.setText("");
}
});
@ -84,8 +112,41 @@ public class WikivoyageSearchDialogFragment extends BaseOsmAndDialogFragment {
return mainView;
}
private void runSearch() {
adapter.setItems(dbHelper.search((searchEt).getText().toString()));
@Override
public void onResume() {
super.onResume();
if (searchCore != null) {
searchCore.registerListener(this);
}
}
@Override
public void onPause() {
super.onPause();
if (searchCore != null) {
searchCore.unregisterListener(this);
searchCore.cancelSearch();
}
}
@Override
public void onSearchStarted() {
switchProgressBarVisibility(true);
}
@Override
public void onSearchFinished(@Nullable List<SearchResult> results, boolean lastTask, boolean cancelled) {
if (!cancelled) {
adapter.setItems(results);
}
if (lastTask) {
switchProgressBarVisibility(false);
}
}
private void switchProgressBarVisibility(boolean show) {
progressBar.setVisibility(show ? View.VISIBLE : View.GONE);
clearIb.setVisibility(show ? View.GONE : View.VISIBLE);
}
public static boolean showInstance(FragmentManager fm) {