diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e10f566d..cf52eba46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Wikimedia Commons for Android +## v1.8 +- New feature: Improved category search function (not limited to prefix search now) + ## v1.7 - Fixed bug with uploading images in Marshmallow - Fixed links in About page diff --git a/commons/AndroidManifest.xml b/commons/AndroidManifest.xml index 7fe5027bc..b33dfca95 100644 --- a/commons/AndroidManifest.xml +++ b/commons/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="24" + android:versionName="1.8" > categories); } ListView categoriesList; - EditText categoriesFilter; + protected EditText categoriesFilter; ProgressBar categoriesSearchInProgress; TextView categoriesNotFoundView; TextView categoriesSkip; CategoriesAdapter categoriesAdapter; - CategoriesUpdater lastUpdater = null; - ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2); private OnCategoriesSaveHandler onCategoriesSaveHandler; - private HashMap> categoriesCache; + protected HashMap> categoriesCache; + + // LHS guarantees ordered insertions, allowing for prioritized method A results + private final Set results = new LinkedHashSet(); + PrefixUpdater prefixUpdaterSub; + MethodAUpdater methodAUpdaterSub; private ContentProviderClient client; - private final int SEARCH_CATS_LIMIT = 25; + protected final static int SEARCH_CATS_LIMIT = 25; private static final String TAG = CategorizationFragment.class.getName(); public static class CategoryItem implements Parcelable { @@ -87,32 +96,55 @@ public class CategorizationFragment extends SherlockFragment{ } } - private class CategoriesUpdater extends AsyncTask> { + protected ArrayList recentCatQuery() { + ArrayList items = new ArrayList(); + ArrayList mergedItems= new ArrayList(); - private String filter; - @Override - protected void onPreExecute() { - super.onPreExecute(); - filter = categoriesFilter.getText().toString(); - categoriesSearchInProgress.setVisibility(View.VISIBLE); - categoriesNotFoundView.setVisibility(View.GONE); + try { + Cursor cursor = client.query( + CategoryContentProvider.BASE_URI, + Category.Table.ALL_FIELDS, + null, + new String[]{}, + Category.Table.COLUMN_LAST_USED + " DESC"); + // fixme add a limit on the original query instead of falling out of the loop? + while (cursor.moveToNext() && cursor.getPosition() < SEARCH_CATS_LIMIT) { + Category cat = Category.fromCursor(cursor); + items.add(cat.getName()); + } + cursor.close(); - categoriesSkip.setVisibility(View.GONE); + if (MwVolleyApi.GpsCatExists.getGpsCatExists() == true) { + //Log.d(TAG, "GPS cats found in CategorizationFragment.java" + MwVolleyApi.getGpsCat().toString()); + List gpsItems = new ArrayList(MwVolleyApi.getGpsCat()); + //Log.d(TAG, "GPS items: " + gpsItems.toString()); + + mergedItems.addAll(gpsItems); + } + + mergedItems.addAll(items); } + catch (RemoteException e) { + throw new RuntimeException(e); + } + //Log.d(TAG, "Merged items: " + mergedItems.toString()); + return mergedItems; + } - @Override - protected void onPostExecute(ArrayList categories) { - super.onPostExecute(categories); + + protected void setCatsAfterAsync(ArrayList categories, String filter) { + + if (getActivity() != null) { ArrayList items = new ArrayList(); HashSet existingKeys = new HashSet(); - for(CategoryItem item : categoriesAdapter.getItems()) { - if(item.selected) { + for (CategoryItem item : categoriesAdapter.getItems()) { + if (item.selected) { items.add(item); existingKeys.add(item.name); } } - for(String category : categories) { - if(!existingKeys.contains(category)) { + for (String category : categories) { + if (!existingKeys.contains(category)) { items.add(new CategoryItem(category, false)); } } @@ -120,8 +152,9 @@ public class CategorizationFragment extends SherlockFragment{ categoriesAdapter.setItems(items); categoriesAdapter.notifyDataSetInvalidated(); categoriesSearchInProgress.setVisibility(View.GONE); - if (categories.size() == 0) { - if(TextUtils.isEmpty(filter)) { + + if (categories.isEmpty()) { + if (TextUtils.isEmpty(filter)) { // If we found no recent cats, show the skip message! categoriesSkip.setVisibility(View.VISIBLE); } else { @@ -132,68 +165,8 @@ public class CategorizationFragment extends SherlockFragment{ categoriesList.smoothScrollToPosition(existingKeys.size()); } } - - @Override - protected ArrayList doInBackground(Void... voids) { - if(TextUtils.isEmpty(filter)) { - ArrayList items = new ArrayList(); - ArrayList mergedItems= new ArrayList(); - - try { - Cursor cursor = client.query( - CategoryContentProvider.BASE_URI, - Category.Table.ALL_FIELDS, - null, - new String[]{}, - Category.Table.COLUMN_LAST_USED + " DESC"); - // fixme add a limit on the original query instead of falling out of the loop? - while (cursor.moveToNext() && cursor.getPosition() < SEARCH_CATS_LIMIT) { - Category cat = Category.fromCursor(cursor); - items.add(cat.getName()); - } - - if (MwVolleyApi.GpsCatExists.getGpsCatExists() == true){ - Log.d(TAG, "GPS cats found in CategorizationFragment.java" + MwVolleyApi.getGpsCat().toString()); - List gpsItems = new ArrayList(MwVolleyApi.getGpsCat()); - Log.d(TAG, "GPS items: " + gpsItems.toString()); - - mergedItems.addAll(gpsItems); - } - - mergedItems.addAll(items); - } - catch (RemoteException e) { - // faaaail - throw new RuntimeException(e); - } - Log.d(TAG, "Merged items: " + mergedItems.toString()); - return mergedItems; - } - - if(categoriesCache.containsKey(filter)) { - return categoriesCache.get(filter); - } - MWApi api = CommonsApplication.createMWApi(); - ApiResult result; - ArrayList categories = new ArrayList(); - try { - result = api.action("query") - .param("list", "allcategories") - .param("acprefix", filter) - .param("aclimit", SEARCH_CATS_LIMIT) - .get(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - ArrayList categoryNodes = result.getNodes("/api/query/allcategories/c"); - for(ApiResult categoryNode: categoryNodes) { - categories.add(categoryNode.getDocument().getTextContent()); - } - - categoriesCache.put(filter, categories); - - return categories; + else { + Log.e(TAG, "Error: Fragment is null"); } } @@ -363,12 +336,70 @@ public class CategorizationFragment extends SherlockFragment{ return rootView; } + private void requestSearchResults() { + + final CountDownLatch latch = new CountDownLatch(1); + + prefixUpdaterSub = new PrefixUpdater(this) { + @Override + protected ArrayList doInBackground(Void... voids) { + ArrayList result = new ArrayList(); + try { + result = super.doInBackground(); + latch.await(); + } + catch (InterruptedException e) { + Log.w(TAG, e); + //Thread.currentThread().interrupt(); + } + return result; + } + + @Override + protected void onPostExecute(ArrayList result) { + super.onPostExecute(result); + + results.addAll(result); + Log.d(TAG, "Prefix result: " + result); + + String filter = categoriesFilter.getText().toString(); + ArrayList resultsList = new ArrayList(results); + categoriesCache.put(filter, resultsList); + Log.d(TAG, "Final results List: " + resultsList); + + categoriesAdapter.notifyDataSetChanged(); + setCatsAfterAsync(resultsList, filter); + } + }; + + methodAUpdaterSub = new MethodAUpdater(this) { + @Override + protected void onPostExecute(ArrayList result) { + results.clear(); + super.onPostExecute(result); + + results.addAll(result); + Log.d(TAG, "Method A result: " + result); + categoriesAdapter.notifyDataSetChanged(); + + latch.countDown(); + } + }; + Utils.executeAsyncTask(prefixUpdaterSub); + Utils.executeAsyncTask(methodAUpdaterSub); + } + private void startUpdatingCategoryList() { - if (lastUpdater != null) { - lastUpdater.cancel(true); + + if (prefixUpdaterSub != null) { + prefixUpdaterSub.cancel(true); } - lastUpdater = new CategoriesUpdater(); - Utils.executeAsyncTask(lastUpdater, executor); + + if (methodAUpdaterSub != null) { + methodAUpdaterSub.cancel(true); + } + + requestSearchResults(); } @Override diff --git a/commons/src/main/java/fr/free/nrw/commons/category/MethodAUpdater.java b/commons/src/main/java/fr/free/nrw/commons/category/MethodAUpdater.java new file mode 100644 index 000000000..5c7ef5735 --- /dev/null +++ b/commons/src/main/java/fr/free/nrw/commons/category/MethodAUpdater.java @@ -0,0 +1,78 @@ +package fr.free.nrw.commons.category; + + +import android.os.AsyncTask; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; + +import org.mediawiki.api.ApiResult; +import org.mediawiki.api.MWApi; + +import java.io.IOException; +import java.util.ArrayList; + +import fr.free.nrw.commons.CommonsApplication; + +public class MethodAUpdater extends AsyncTask> { + + private String filter; + private static final String TAG = MethodAUpdater.class.getName(); + CategorizationFragment catFragment; + + public MethodAUpdater(CategorizationFragment catFragment) { + this.catFragment = catFragment; + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + filter = catFragment.categoriesFilter.getText().toString(); + catFragment.categoriesSearchInProgress.setVisibility(View.VISIBLE); + catFragment.categoriesNotFoundView.setVisibility(View.GONE); + + catFragment.categoriesSkip.setVisibility(View.GONE); + } + + @Override + protected ArrayList doInBackground(Void... voids) { + //If user hasn't typed anything in yet, get GPS and recent items + if(TextUtils.isEmpty(filter)) { + return catFragment.recentCatQuery(); + } + + //if user types in something that is in cache, return cached category + if(catFragment.categoriesCache.containsKey(filter)) { + return catFragment.categoriesCache.get(filter); + } + + //otherwise if user has typed something in that isn't in cache, search API for matching categories + MWApi api = CommonsApplication.createMWApi(); + ApiResult result; + ArrayList categories = new ArrayList(); + + //URL https://commons.wikimedia.org/w/api.php?action=query&format=xml&list=search&srwhat=text&srenablerewrites=1&srnamespace=14&srlimit=10&srsearch= + try { + result = api.action("query") + .param("format", "xml") + .param("list", "search") + .param("srwhat", "text") + .param("srnamespace", "14") + .param("srlimit", catFragment.SEARCH_CATS_LIMIT) + .param("srsearch", filter) + .get(); + Log.d(TAG, "Method A URL filter" + result.toString()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + ArrayList categoryNodes = result.getNodes("/api/query/search/p/@title"); + for(ApiResult categoryNode: categoryNodes) { + String cat = categoryNode.getDocument().getTextContent(); + String catString = cat.replace("Category:", ""); + categories.add(catString); + } + + return categories; + } +} diff --git a/commons/src/main/java/fr/free/nrw/commons/category/PrefixUpdater.java b/commons/src/main/java/fr/free/nrw/commons/category/PrefixUpdater.java new file mode 100644 index 000000000..589075837 --- /dev/null +++ b/commons/src/main/java/fr/free/nrw/commons/category/PrefixUpdater.java @@ -0,0 +1,70 @@ +package fr.free.nrw.commons.category; + +import android.os.AsyncTask; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; + +import org.mediawiki.api.ApiResult; +import org.mediawiki.api.MWApi; + +import java.io.IOException; +import java.util.ArrayList; + +import fr.free.nrw.commons.CommonsApplication; + +public class PrefixUpdater extends AsyncTask> { + + private String filter; + private static final String TAG = PrefixUpdater.class.getName(); + private CategorizationFragment catFragment; + + public PrefixUpdater(CategorizationFragment catFragment) { + this.catFragment = catFragment; + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + filter = catFragment.categoriesFilter.getText().toString(); + catFragment.categoriesSearchInProgress.setVisibility(View.VISIBLE); + catFragment.categoriesNotFoundView.setVisibility(View.GONE); + + catFragment.categoriesSkip.setVisibility(View.GONE); + } + + @Override + protected ArrayList doInBackground(Void... voids) { + //If user hasn't typed anything in yet, get GPS and recent items + if(TextUtils.isEmpty(filter)) { + return catFragment.recentCatQuery(); + } + + //if user types in something that is in cache, return cached category + if(catFragment.categoriesCache.containsKey(filter)) { + return catFragment.categoriesCache.get(filter); + } + + //otherwise if user has typed something in that isn't in cache, search API for matching categories + MWApi api = CommonsApplication.createMWApi(); + ApiResult result; + ArrayList categories = new ArrayList(); + try { + result = api.action("query") + .param("list", "allcategories") + .param("acprefix", filter) + .param("aclimit", catFragment.SEARCH_CATS_LIMIT) + .get(); + Log.d(TAG, "Prefix URL filter" + result.toString()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + ArrayList categoryNodes = result.getNodes("/api/query/allcategories/c"); + for(ApiResult categoryNode: categoryNodes) { + categories.add(categoryNode.getDocument().getTextContent()); + } + + return categories; + } +}