Update list counts

Closes #22
pull/120/head
Alex Baker 12 years ago
parent 62c280e532
commit 9789ec05db

@ -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
* <li> EXTRAS_TASK_ID id of the task

@ -219,4 +219,9 @@ public class Filter extends FilterListItem {
return new Filter[size];
}
};
@Override
public String toString() {
return title;
}
}

@ -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();
}
}

@ -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();
}
}

@ -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<Filter> {
@Autowired
private TaskService taskService;
@Autowired
private FilterCounter filterCounter;
/** parent activity */
protected final Activity activity;
@ -81,6 +80,8 @@ public class FilterAdapter extends ArrayAdapter<Filter> {
/** 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<Filter> {
/** 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<Filter, Integer> 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<Runnable>());
public FilterAdapter(Activity activity, int rowLayout) {
this(activity, null, rowLayout, false, false);
}
@ -122,7 +109,6 @@ public class FilterAdapter extends ArrayAdapter<Filter> {
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<Filter> {
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<Filter> {
@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<Filter> {
}
}
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<Filter> {
@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<Filter> {
* ============================================================= 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<Filter> {
}
}
notifyDataSetChanged();
filterCounter.refreshFilterCounts(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
}
}
@ -403,7 +351,10 @@ public class FilterAdapter extends ArrayAdapter<Filter> {
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<Filter> {
*/
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<Filter> {
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;
}

@ -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(),

@ -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;

@ -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<Task> 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 {
* <li>!4 - set priority to !!!!
*/
private void quickAdd(Task task, List<String> 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));
}
});
}
/**

@ -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<Filter, Integer> filterCounts = new ConcurrentHashMap<>();
@Autowired
private TaskDao taskDao;
public FilterCounter() {
this(new ThreadPoolExecutor(0, 1, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()));
}
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<Task> cursor = taskDao.query(
Query.select(Task.ID).withQueryTemplate(queryTemplate));
try {
return cursor.getCount();
} finally {
cursor.close();
}
}
}
Loading…
Cancel
Save