From 9789ec05db5e788808ca4ba302db3b631a69bf8b Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Sun, 2 Feb 2014 15:11:47 -0600 Subject: [PATCH] Update list counts Closes #22 --- .../astrid/api/AstridApiConstants.java | 2 + .../java/com/todoroo/astrid/api/Filter.java | 5 + .../astrid/activity/TaskListActivity.java | 18 +- .../astrid/activity/TaskListFragment.java | 17 +- .../todoroo/astrid/adapter/FilterAdapter.java | 164 ++++++------------ .../service/AstridDependencyInjector.java | 3 + .../astrid/service/StartupService.java | 2 + .../todoroo/astrid/service/TaskService.java | 49 ++++-- .../java/org/tasks/filters/FilterCounter.java | 81 +++++++++ 9 files changed, 183 insertions(+), 158 deletions(-) create mode 100644 astrid/src/main/java/org/tasks/filters/FilterCounter.java 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(); + } + } +}