diff --git a/api/src/main/java/com/todoroo/astrid/api/AstridApiConstants.java b/api/src/main/java/com/todoroo/astrid/api/AstridApiConstants.java
index 6355d311a..6e8038532 100644
--- a/api/src/main/java/com/todoroo/astrid/api/AstridApiConstants.java
+++ b/api/src/main/java/com/todoroo/astrid/api/AstridApiConstants.java
@@ -201,6 +201,8 @@ public class AstridApiConstants {
*/
public static final String BROADCAST_EVENT_TASK_LIST_UPDATED = API_PACKAGE + ".TASK_LIST_UPDATED";
+ public static final String BROADCAST_EVENT_FILTER_LIST_UPDATED = API_PACKAGE + ".FILTER_LIST_UPDATED";
+
/**
* Action name for broadcast intent notifying that task was completed
*
EXTRAS_TASK_ID id of the task
diff --git a/api/src/main/java/com/todoroo/astrid/api/Filter.java b/api/src/main/java/com/todoroo/astrid/api/Filter.java
index bf94eef43..1ea7e977f 100644
--- a/api/src/main/java/com/todoroo/astrid/api/Filter.java
+++ b/api/src/main/java/com/todoroo/astrid/api/Filter.java
@@ -219,4 +219,9 @@ public class Filter extends FilterListItem {
return new Filter[size];
}
};
+
+ @Override
+ public String toString() {
+ return title;
+ }
}
diff --git a/astrid/src/main/java/com/todoroo/astrid/activity/TaskListActivity.java b/astrid/src/main/java/com/todoroo/astrid/activity/TaskListActivity.java
index a6051d549..ccb9ebdcf 100644
--- a/astrid/src/main/java/com/todoroo/astrid/activity/TaskListActivity.java
+++ b/astrid/src/main/java/com/todoroo/astrid/activity/TaskListActivity.java
@@ -456,24 +456,10 @@ public class TaskListActivity extends AstridActivity implements OnPageChangeList
}
}
- public void incrementFilterCount(Filter filter) {
+ public void refreshFilterCount() {
FilterListFragment flf = getFilterListFragment();
if (flf != null) {
- flf.adapter.incrementFilterCount(filter);
- }
- }
-
- public void decrementFilterCount(Filter filter) {
- FilterListFragment flf = getFilterListFragment();
- if (flf != null) {
- flf.adapter.decrementFilterCount(filter);
- }
- }
-
- public void refreshFilterCount(Filter filter) {
- FilterListFragment flf = getFilterListFragment();
- if (flf != null) {
- flf.adapter.refreshFilterCount(filter);
+ flf.adapter.refreshFilterCount();
}
}
diff --git a/astrid/src/main/java/com/todoroo/astrid/activity/TaskListFragment.java b/astrid/src/main/java/com/todoroo/astrid/activity/TaskListFragment.java
index 24716352f..35a3ecb92 100644
--- a/astrid/src/main/java/com/todoroo/astrid/activity/TaskListFragment.java
+++ b/astrid/src/main/java/com/todoroo/astrid/activity/TaskListFragment.java
@@ -956,12 +956,9 @@ public class TaskListFragment extends ListFragment implements OnSortSelectedList
}
public void onTaskCreated(Task task) {
- incrementFilterCount();
}
protected void onTaskDelete(Task task) {
- decrementFilterCount();
-
Activity a = getActivity();
if (a instanceof AstridActivity) {
AstridActivity activity = (AstridActivity) a;
@@ -975,21 +972,9 @@ public class TaskListFragment extends ListFragment implements OnSortSelectedList
TimerPlugin.updateTimer(ContextManager.getContext(), task, false);
}
- public void incrementFilterCount() {
- if (getActivity() instanceof TaskListActivity) {
- ((TaskListActivity) getActivity()).incrementFilterCount(filter);
- }
- }
-
- public void decrementFilterCount() {
- if (getActivity() instanceof TaskListActivity) {
- ((TaskListActivity) getActivity()).decrementFilterCount(filter);
- }
- }
-
public void refreshFilterCount() {
if (getActivity() instanceof TaskListActivity) {
- ((TaskListActivity) getActivity()).refreshFilterCount(filter);
+ ((TaskListActivity) getActivity()).refreshFilterCount();
}
}
diff --git a/astrid/src/main/java/com/todoroo/astrid/adapter/FilterAdapter.java b/astrid/src/main/java/com/todoroo/astrid/adapter/FilterAdapter.java
index b6e5c6e9a..bb92e95bf 100644
--- a/astrid/src/main/java/com/todoroo/astrid/adapter/FilterAdapter.java
+++ b/astrid/src/main/java/com/todoroo/astrid/adapter/FilterAdapter.java
@@ -19,7 +19,6 @@ import android.os.Bundle;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -48,12 +47,9 @@ import com.todoroo.astrid.api.FilterWithUpdate;
import com.todoroo.astrid.service.TaskService;
import org.tasks.R;
+import org.tasks.filters.FilterCounter;
-import java.util.HashMap;
import java.util.List;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -69,6 +65,9 @@ public class FilterAdapter extends ArrayAdapter {
@Autowired
private TaskService taskService;
+ @Autowired
+ private FilterCounter filterCounter;
+
/** parent activity */
protected final Activity activity;
@@ -81,6 +80,8 @@ public class FilterAdapter extends ArrayAdapter {
/** receiver for new filters */
protected final FilterReceiver filterReceiver = new FilterReceiver();
+ private final FilterListUpdateReceiver filterListUpdateReceiver = new FilterListUpdateReceiver();
+
/** row layout to inflate */
private final int layout;
@@ -93,20 +94,6 @@ public class FilterAdapter extends ArrayAdapter {
/** whether rows are selectable */
private final boolean selectable;
- /** Pattern for matching filter counts in listing titles */
- private final Pattern countPattern = Pattern.compile(".* \\((\\d+)\\)$"); //$NON-NLS-1$
-
- private final HashMap filterCounts;
-
- // Previous solution involved a queue of filters and a filterSizeLoadingThread. The filterSizeLoadingThread had
- // a few problems: how to make sure that the thread is resumed when the controlling activity is resumed, and
- // how to make sure that the the filterQueue does not accumulate filters without being processed. I am replacing
- // both the queue and a the thread with a thread pool, which will shut itself off after a second if it has
- // nothing to do (corePoolSize == 0, which makes it available for garbage collection), and will wake itself up
- // if new filters are queued (obviously it cannot be garbage collected if it is possible for new filters to
- // be added).
- private final ThreadPoolExecutor filterExecutor = new ThreadPoolExecutor(0, 1, 1, TimeUnit.SECONDS, new LinkedBlockingQueue());
-
public FilterAdapter(Activity activity, int rowLayout) {
this(activity, null, rowLayout, false, false);
}
@@ -122,7 +109,6 @@ public class FilterAdapter extends ArrayAdapter {
this.layout = rowLayout;
this.skipIntentFilters = skipIntentFilters;
this.selectable = selectable;
- this.filterCounts = new HashMap<>();
inflater = (LayoutInflater) activity.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
@@ -134,40 +120,6 @@ public class FilterAdapter extends ArrayAdapter {
if(selectable && selection == null) {
setSelection(filter);
}
- filterExecutor.submit(new Runnable() {
- @Override
- public void run() {
- try {
- int size = -1;
- Matcher m = countPattern.matcher(filter.listingTitle);
- if(m.find()) {
- String countString = m.group(1);
- try {
- size = Integer.parseInt(countString);
- } catch (NumberFormatException e) {
- // Count manually
- e.printStackTrace();
- }
- }
-
- if (size < 0) {
- size = taskService.countTasks(filter);
- filter.listingTitle = filter.listingTitle + (" (" + //$NON-NLS-1$
- size + ")"); //$NON-NLS-1$
- }
-
- filterCounts.put(filter, size);
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- notifyDataSetChanged();
- }
- });
- } catch (Exception e) {
- Log.e("astrid-filter-adapter", "Error loading filter size", e); //$NON-NLS-1$ //$NON-NLS-2$
- }
- }
- });
}
@Override
@@ -178,9 +130,20 @@ public class FilterAdapter extends ArrayAdapter {
@Override
public void add(Filter item) {
super.add(item);
- notifyDataSetChanged();
// load sizes
+ filterCounter.registerFilter(item);
offerFilter(item);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ FilterAdapter.super.notifyDataSetChanged();
+ }
+ });
}
public void addOrLookup(Filter filter) {
@@ -203,37 +166,11 @@ public class FilterAdapter extends ArrayAdapter {
}
}
- public void adjustFilterCount(Filter filter, int delta) {
- int filterCount = 0;
- if (filterCounts.containsKey(filter)) {
- filterCount = filterCounts.get(filter);
- }
- int newCount = Math.max(filterCount + delta, 0);
- filterCounts.put(filter, newCount);
- notifyDataSetChanged();
- }
-
- public void incrementFilterCount(Filter filter) {
- adjustFilterCount(filter, 1);
- }
-
- public void decrementFilterCount(Filter filter) {
- adjustFilterCount(filter, -1);
- }
-
- public void refreshFilterCount(final Filter filter) {
- filterExecutor.submit(new Runnable() {
+ public void refreshFilterCount() {
+ filterCounter.refreshFilterCounts(new Runnable() {
@Override
public void run() {
- int size = taskService.countTasks(filter);
- filterCounts.put(filter, size);
- activity.runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- notifyDataSetChanged();
- }
- });
+ notifyDataSetChanged();
}
});
}
@@ -271,7 +208,6 @@ public class FilterAdapter extends ArrayAdapter {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
-
convertView = newView(convertView, parent);
ViewHolder viewHolder = (ViewHolder) convertView.getTag();
viewHolder.item = getItem(position);
@@ -327,6 +263,13 @@ public class FilterAdapter extends ArrayAdapter {
* ============================================================= receiver
* ====================================================================== */
+ public class FilterListUpdateReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ notifyDataSetChanged();
+ }
+ }
+
/**
* Receiver which receives intents to add items to the filter list
*
@@ -383,7 +326,12 @@ public class FilterAdapter extends ArrayAdapter {
}
}
- notifyDataSetChanged();
+ filterCounter.refreshFilterCounts(new Runnable() {
+ @Override
+ public void run() {
+ notifyDataSetChanged();
+ }
+ });
}
}
@@ -403,7 +351,10 @@ public class FilterAdapter extends ArrayAdapter {
IntentFilter regularFilter = new IntentFilter(AstridApiConstants.BROADCAST_SEND_FILTERS);
regularFilter.setPriority(2);
activity.registerReceiver(filterReceiver, regularFilter);
+ activity.registerReceiver(filterListUpdateReceiver, new IntentFilter(AstridApiConstants.BROADCAST_EVENT_FILTER_LIST_UPDATED));
getLists();
+
+ refreshFilterCount();
}
/**
@@ -411,12 +362,16 @@ public class FilterAdapter extends ArrayAdapter {
*/
public void unregisterRecevier() {
activity.unregisterReceiver(filterReceiver);
+ activity.unregisterReceiver(filterListUpdateReceiver);
}
/* ======================================================================
* ================================================================ views
* ====================================================================== */
+ /** Pattern for matching filter counts in listing titles */
+ private final Pattern countPattern = Pattern.compile(".* \\((\\d+)\\)$"); //$NON-NLS-1$
+
public void populateView(ViewHolder viewHolder) {
FilterListItem filter = viewHolder.item;
if(filter == null) {
@@ -438,36 +393,29 @@ public class FilterAdapter extends ArrayAdapter {
viewHolder.name.setShadowLayer(0, 0, 0, 0);
}
+ String title = filter.listingTitle;
+ Matcher match = countPattern.matcher(filter.listingTitle);
+ if(match.matches()) {
+ title = title.substring(0, title.lastIndexOf(' '));
+ }
+ if(!title.equals(viewHolder.name.getText())) {
+ viewHolder.name.setText(title);
+ }
+
// title / size
int countInt = -1;
- if(filterCounts.containsKey(filter) || (!TextUtils.isEmpty(filter.listingTitle) && filter.listingTitle.matches(".* \\(\\d+\\)$"))) { //$NON-NLS-1$
+ if(filterCounter.containsKey(filter) || (!TextUtils.isEmpty(filter.listingTitle) && filter.listingTitle.matches(".* \\(\\d+\\)$"))) { //$NON-NLS-1$
viewHolder.size.setVisibility(View.VISIBLE);
- String count;
- if (filterCounts.containsKey(filter)) {
- Integer c = filterCounts.get(filter);
+ String count = "";
+ if (filterCounter.containsKey(filter)) {
+ Integer c = filterCounter.get(filter);
countInt = c;
count = c.toString();
- } else {
- count = filter.listingTitle.substring(filter.listingTitle.lastIndexOf('(') + 1,
- filter.listingTitle.length() - 1);
- try {
- countInt = Integer.parseInt(count);
- } catch (NumberFormatException e) {
- //
- }
}
- viewHolder.size.setText(count);
-
- String title;
- int listingTitleSplit = filter.listingTitle.lastIndexOf(' ');
- if (listingTitleSplit > 0) {
- title = filter.listingTitle.substring(0, listingTitleSplit);
- } else {
- title = filter.listingTitle;
+ if(!count.equals(viewHolder.size.getText())) {
+ viewHolder.size.setText(count);
}
- viewHolder.name.setText(title);
} else {
- viewHolder.name.setText(filter.listingTitle);
viewHolder.size.setVisibility(View.GONE);
countInt = -1;
}
diff --git a/astrid/src/main/java/com/todoroo/astrid/service/AstridDependencyInjector.java b/astrid/src/main/java/com/todoroo/astrid/service/AstridDependencyInjector.java
index b14d037f0..6fec335a2 100644
--- a/astrid/src/main/java/com/todoroo/astrid/service/AstridDependencyInjector.java
+++ b/astrid/src/main/java/com/todoroo/astrid/service/AstridDependencyInjector.java
@@ -27,6 +27,7 @@ import com.todoroo.astrid.tags.TagService;
import com.todoroo.astrid.utility.Constants;
import org.tasks.Broadcaster;
+import org.tasks.filters.FilterCounter;
/**
* Astrid application dependency injector loads classes in Astrid with the
@@ -86,6 +87,8 @@ public class AstridDependencyInjector extends AbstractDependencyInjector {
injectables.put("broadcaster", Broadcaster.class);
+ injectables.put("filterCounter", FilterCounter.class);
+
// these make reference to fields defined above
injectables.put("errorReporters", new ErrorReporter[] {
new AndroidLogReporter(),
diff --git a/astrid/src/main/java/com/todoroo/astrid/service/StartupService.java b/astrid/src/main/java/com/todoroo/astrid/service/StartupService.java
index bf81b09b8..128492412 100644
--- a/astrid/src/main/java/com/todoroo/astrid/service/StartupService.java
+++ b/astrid/src/main/java/com/todoroo/astrid/service/StartupService.java
@@ -39,6 +39,7 @@ import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.TagDataDao;
+import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
@@ -51,6 +52,7 @@ import com.todoroo.astrid.utility.AstridPreferences;
import com.todoroo.astrid.utility.Constants;
import org.tasks.R;
+import org.tasks.filters.FilterCounter;
import java.io.File;
import java.util.List;
diff --git a/astrid/src/main/java/com/todoroo/astrid/service/TaskService.java b/astrid/src/main/java/com/todoroo/astrid/service/TaskService.java
index 37a18134a..505735755 100644
--- a/astrid/src/main/java/com/todoroo/astrid/service/TaskService.java
+++ b/astrid/src/main/java/com/todoroo/astrid/service/TaskService.java
@@ -6,6 +6,7 @@
package com.todoroo.astrid.service;
import android.content.ContentValues;
+import android.content.Intent;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.TodorooCursor;
@@ -19,7 +20,7 @@ import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
import com.todoroo.astrid.adapter.UpdateAdapter;
-import com.todoroo.astrid.api.Filter;
+import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.PermaSql;
import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.dao.MetadataDao;
@@ -38,6 +39,9 @@ import com.todoroo.astrid.tags.TagService;
import com.todoroo.astrid.tags.TaskToTagMetadata;
import com.todoroo.astrid.utility.TitleParser;
+import org.tasks.Broadcaster;
+import org.tasks.filters.FilterCounter;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
@@ -72,6 +76,12 @@ public class TaskService {
@Autowired
private UserActivityDao userActivityDao;
+ @Autowired
+ private Broadcaster broadcaster;
+
+ @Autowired
+ private FilterCounter filterCounter;
+
public TaskService() {
DependencyInjectionService.getInstance().inject(this);
}
@@ -102,7 +112,7 @@ public class TaskService {
item.setCompletionDate(0L);
}
- taskDao.save(item);
+ save(item);
}
/**
@@ -110,6 +120,11 @@ public class TaskService {
*/
public void save(Task item) {
taskDao.save(item);
+ broadcastFilterListUpdated();
+ }
+
+ private void saveWithoutPublishingFilterUpdate(Task item) {
+ taskDao.save(item);
}
/**
@@ -130,7 +145,7 @@ public class TaskService {
if(cursor.getCount() > 0) {
Metadata metadata = new Metadata();
newTask.putTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC, true);
- taskDao.save(newTask);
+ save(newTask);
long newId = newTask.getId();
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
metadata.readFromCursor(cursor);
@@ -172,7 +187,7 @@ public class TaskService {
item.setId(id);
GCalHelper.deleteTaskEvent(item);
item.setDeletionDate(DateUtilities.now());
- taskDao.save(item);
+ save(item);
}
}
@@ -265,7 +280,7 @@ public class TaskService {
try {
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
taskValues.setID(cursor.get(Task.ID));
- taskDao.save(taskValues);
+ save(taskValues);
}
return cursor.getCount();
} finally {
@@ -302,18 +317,6 @@ public class TaskService {
}
}
- /** count tasks in a given filter */
- public int countTasks(Filter filter) {
- String queryTemplate = PermaSql.replacePlaceholders(filter.getSqlQuery());
- TodorooCursor cursor = query(Query.select(Task.ID).withQueryTemplate(
- queryTemplate));
- try {
- return cursor.getCount();
- } finally {
- cursor.close();
- }
- }
-
/**
* Delete all tasks matching a given criterion
*/
@@ -329,10 +332,20 @@ public class TaskService {
* !4 - set priority to !!!!
*/
private void quickAdd(Task task, List tags) {
- save(task);
+ saveWithoutPublishingFilterUpdate(task);
for(String tag : tags) {
TagService.getInstance().createLink(task, tag);
}
+ broadcastFilterListUpdated();
+ }
+
+ private void broadcastFilterListUpdated() {
+ filterCounter.refreshFilterCounts(new Runnable() {
+ @Override
+ public void run() {
+ broadcaster.sendOrderedBroadcast(new Intent(AstridApiConstants.BROADCAST_EVENT_FILTER_LIST_UPDATED));
+ }
+ });
}
/**
diff --git a/astrid/src/main/java/org/tasks/filters/FilterCounter.java b/astrid/src/main/java/org/tasks/filters/FilterCounter.java
new file mode 100644
index 000000000..6790f048f
--- /dev/null
+++ b/astrid/src/main/java/org/tasks/filters/FilterCounter.java
@@ -0,0 +1,81 @@
+package org.tasks.filters;
+
+import com.todoroo.andlib.data.TodorooCursor;
+import com.todoroo.andlib.service.Autowired;
+import com.todoroo.andlib.service.DependencyInjectionService;
+import com.todoroo.andlib.sql.Query;
+import com.todoroo.astrid.api.Filter;
+import com.todoroo.astrid.api.FilterListItem;
+import com.todoroo.astrid.api.PermaSql;
+import com.todoroo.astrid.dao.TaskDao;
+import com.todoroo.astrid.data.Task;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class FilterCounter {
+ // Previous solution involved a queue of filters and a filterSizeLoadingThread. The filterSizeLoadingThread had
+ // a few problems: how to make sure that the thread is resumed when the controlling activity is resumed, and
+ // how to make sure that the the filterQueue does not accumulate filters without being processed. I am replacing
+ // both the queue and a the thread with a thread pool, which will shut itself off after a second if it has
+ // nothing to do (corePoolSize == 0, which makes it available for garbage collection), and will wake itself up
+ // if new filters are queued (obviously it cannot be garbage collected if it is possible for new filters to
+ // be added).
+ private final ExecutorService executorService;
+
+ private final Map filterCounts = new ConcurrentHashMap<>();
+
+ @Autowired
+ private TaskDao taskDao;
+
+ public FilterCounter() {
+ this(new ThreadPoolExecutor(0, 1, 1, TimeUnit.SECONDS, new LinkedBlockingQueue()));
+ }
+
+ FilterCounter(ExecutorService executorService) {
+ this.executorService = executorService;
+ DependencyInjectionService.getInstance().inject(this);
+ }
+
+ public void refreshFilterCounts(final Runnable onComplete) {
+ executorService.submit(new Runnable() {
+ @Override
+ public void run() {
+ for (Filter filter : filterCounts.keySet()) {
+ int size = countTasks(filter);
+ filterCounts.put(filter, size);
+ }
+ if (onComplete != null) {
+ onComplete.run();
+ }
+ }
+ });
+ }
+
+ public void registerFilter(Filter filter) {
+ filterCounts.put(filter, 0);
+ }
+
+ public boolean containsKey(FilterListItem filter) {
+ return filterCounts.containsKey(filter);
+ }
+
+ public Integer get(FilterListItem filter) {
+ return filterCounts.get(filter);
+ }
+
+ private int countTasks(Filter filter) {
+ String queryTemplate = PermaSql.replacePlaceholders(filter.getSqlQuery());
+ TodorooCursor cursor = taskDao.query(
+ Query.select(Task.ID).withQueryTemplate(queryTemplate));
+ try {
+ return cursor.getCount();
+ } finally {
+ cursor.close();
+ }
+ }
+}