From baa79f3424c00fcf829e0394162851cdd4a9ebcd Mon Sep 17 00:00:00 2001 From: Arne Jans Date: Sat, 14 Aug 2010 17:37:16 +0200 Subject: [PATCH 01/17] Added the ProducteevFilterExposer --- astrid/AndroidManifest.xml | 8 +++++++- astrid/default.properties | 2 +- astrid/res/values/strings-producteev.xml | 12 ++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index 750dd7362..14f52fdd3 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -53,7 +53,7 @@ + android:label="@string/app_name" android:debuggable="true"> @@ -317,6 +317,12 @@ android:clearTaskOnLaunch="true" /> + + + + + + diff --git a/astrid/default.properties b/astrid/default.properties index c5d5335ee..dab739e94 100644 --- a/astrid/default.properties +++ b/astrid/default.properties @@ -10,5 +10,5 @@ # Indicates whether an apk should be generated for each density. split.density=false # Project target. -target=android-8 +target=Motorola, Inc.:MILESTONE:7 apk-configurations= diff --git a/astrid/res/values/strings-producteev.xml b/astrid/res/values/strings-producteev.xml index a6145a6e4..ef9d1b684 100644 --- a/astrid/res/values/strings-producteev.xml +++ b/astrid/res/values/strings-producteev.xml @@ -10,6 +10,18 @@ R: %s + + Producteev + + + Producteev Workspaces + + + $N + + + Workspace \'%s\' + From 818622156e0d56fda9dba8b947f107eb96438eab Mon Sep 17 00:00:00 2001 From: Arne Jans Date: Sat, 14 Aug 2010 17:37:43 +0200 Subject: [PATCH 02/17] Added the ProducteevFilterExposer --- .../producteev/ProducteevFilterExposer.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevFilterExposer.java diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevFilterExposer.java new file mode 100644 index 000000000..ea5fd583b --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevFilterExposer.java @@ -0,0 +1,87 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.producteev; + +import android.content.BroadcastReceiver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; + +import com.timsu.astrid.R; +import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.sql.QueryTemplate; +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.api.Filter; +import com.todoroo.astrid.api.FilterCategory; +import com.todoroo.astrid.api.FilterListHeader; +import com.todoroo.astrid.api.FilterListItem; +import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; +import com.todoroo.astrid.model.Metadata; +import com.todoroo.astrid.model.StoreObject; +import com.todoroo.astrid.producteev.sync.ProducteevDashboard; +import com.todoroo.astrid.producteev.sync.ProducteevDataService; +import com.todoroo.astrid.producteev.sync.ProducteevTask; + +/** + * Exposes filters based on Producteev Dashboards + * + * @author Arne Jans + * + */ +public class ProducteevFilterExposer extends BroadcastReceiver { + + @SuppressWarnings("nls") + private Filter filterFromList(Context context, ProducteevDashboard dashboard) { + String dashboardTitle = context.getString(R.string.producteev_FEx_dashboard_item). + replace("$N", dashboard.getName()); + String title = context.getString(R.string.producteev_FEx_dashboard_title, dashboard.getName()); + ContentValues values = new ContentValues(); + values.put(Metadata.KEY.name, ProducteevTask.METADATA_KEY); + values.put(ProducteevTask.DASHBOARD_ID.name, dashboard.getId()); + values.put(ProducteevTask.ID.name, 0); + values.put(ProducteevTask.CREATOR_ID.name, 0); + values.put(ProducteevTask.RESPONSIBLE_ID.name, 0); + Filter filter = new Filter(dashboardTitle, title, new QueryTemplate().join( + ProducteevDataService.METADATA_JOIN).where(Criterion.and( + MetadataCriteria.withKey(ProducteevTask.METADATA_KEY), + TaskCriteria.isActive(), + TaskCriteria.isVisible(), + ProducteevTask.DASHBOARD_ID.eq(dashboard.getId()))), + values); + + return filter; + } + + @Override + public void onReceive(Context context, Intent intent) { + // if we aren't logged in, don't expose features + if(!ProducteevUtilities.INSTANCE.isLoggedIn()) + return; + + StoreObject[] dashboards = ProducteevDataService.getInstance().getDashboards(); + + // If user does not have any tags, don't show this section at all + if(dashboards.length == 0) + return; + + Filter[] dashboardFilters = new Filter[dashboards.length]; + for(int i = 0; i < dashboards.length; i++) + dashboardFilters[i] = filterFromList(context, new ProducteevDashboard(dashboards[i])); + + FilterListHeader producteevHeader = new FilterListHeader(context.getString(R.string.producteev_FEx_header)); + FilterCategory producteevDashboards = new FilterCategory(context.getString(R.string.producteev_FEx_dashboard), + dashboardFilters); + + // transmit filter list + FilterListItem[] list = new FilterListItem[2]; + list[0] = producteevHeader; + list[1] = producteevDashboards; + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, ProducteevUtilities.IDENTIFIER); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, list); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + } + +} From 3f2a2685ca8f8287e0aba8c84517a70b07b8a268 Mon Sep 17 00:00:00 2001 From: Arne Jans Date: Sun, 15 Aug 2010 23:29:33 +0200 Subject: [PATCH 03/17] TaskEditPage: responsibleSpinner gets updated by selecting another dashboard. And its more robust now, if selecting another dashboard, it tries to identify the responsible amongst the associated users and sets it. And if you select the nosync-dashboard, it doesnt get NPEs anymore. Also fixed an invalid whitespace in strings-producteev.xml --- .../producteev/ProducteevControlSet.java | 96 ++++++++++++++----- astrid/res/values/strings-producteev.xml | 4 +- 2 files changed, 74 insertions(+), 26 deletions(-) diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevControlSet.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevControlSet.java index 5c8389499..7b2a6c451 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevControlSet.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevControlSet.java @@ -7,9 +7,11 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Spinner; import android.widget.TextView; +import android.widget.AdapterView.OnItemSelectedListener; import com.timsu.astrid.R; import com.todoroo.andlib.service.Autowired; @@ -55,7 +57,70 @@ public class ProducteevControlSet implements TaskEditControlSet { view = LayoutInflater.from(activity).inflate(R.layout.producteev_control, parent, true); this.responsibleSelector = (Spinner) activity.findViewById(R.id.producteev_TEA_task_assign); + TextView emptyView = new TextView(activity); + emptyView.setText(activity.getText(R.string.producteev_no_dashboard)); + responsibleSelector.setEmptyView(emptyView); + this.dashboardSelector = (Spinner) activity.findViewById(R.id.producteev_TEA_dashboard_assign); + this.dashboardSelector.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView parent, View view, + int position, long id) { + Spinner dashSelector = (Spinner) parent; + ProducteevDashboard dashboard = (ProducteevDashboard) dashSelector.getSelectedItem(); + refreshResponsibleSpinner(dashboard.getUsers()); + } + + @Override + public void onNothingSelected(AdapterView parent) { + responsibleSelector.setAdapter(null); + responsibleSelector.setEnabled(false); + view.findViewById(R.id.producteev_TEA_task_assign_label).setVisibility(View.GONE); + } + }); + } + + /** + * Refresh the content of the responsibleSelector with the given userlist. + * + * @param users the new userlist to show in the responsibleSelector + */ + private void refreshResponsibleSpinner(ArrayList users) { + Metadata metadata = ProducteevDataService.getInstance().getTaskMetadata(myTask.getId()); + Long responsibleId = metadata.getValue(ProducteevTask.RESPONSIBLE_ID); + refreshResponsibleSpinner(users, responsibleId); + } + + /** + * Refresh the content of the responsibleSelector with the given userlist. + * + * @param users the new userlist to show in the responsibleSelector + * @param responsibleId the id of the responsible user to set in the spinner + */ + private void refreshResponsibleSpinner(ArrayList users, Long responsibleId) { + // Fill the responsible-spinner and set the current responsible + this.users = (users == null ? new ArrayList() : users); + + ArrayAdapter usersAdapter = new ArrayAdapter(activity, + android.R.layout.simple_spinner_item, this.users); + usersAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + responsibleSelector.setAdapter(usersAdapter); + if (users == null) + view.findViewById(R.id.producteev_TEA_task_assign_label).setVisibility(View.GONE); + else + view.findViewById(R.id.producteev_TEA_task_assign_label).setVisibility(View.VISIBLE); + + + int responsibleSpinnerIndex = 0; + + for (int i = 0; i < this.users.size() ; i++) { + if (this.users.get(i).getId() == responsibleId) { + responsibleSpinnerIndex=i; + break; + } + } + responsibleSelector.setSelection(responsibleSpinnerIndex); } @Override @@ -71,50 +136,33 @@ public class ProducteevControlSet implements TaskEditControlSet { StoreObject[] dashboardsData = ProducteevDataService.getInstance().getDashboards(); dashboards = new ArrayList(dashboardsData.length); ProducteevDashboard ownerDashboard = null; - int dashboardSpinnerIndex = 0; + int dashboardSpinnerIndex = -1; //dashboard to not sync as first spinner-entry - dashboards.add(new ProducteevDashboard(ProducteevUtilities.DASHBOARD_NO_SYNC, activity.getString(R.string.producteev_no_dashboard),null)); - for (int i=1;i dashAdapter = new ArrayAdapter(activity, android.R.layout.simple_spinner_item, dashboards); dashAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); dashboardSelector.setAdapter(dashAdapter); - dashboardSelector.setSelection(dashboardSpinnerIndex); + dashboardSelector.setSelection(dashboardSpinnerIndex+1); if (ownerDashboard == null || ownerDashboard.getId() == ProducteevUtilities.DASHBOARD_NO_SYNC) { responsibleSelector.setEnabled(false); - TextView emptyView = new TextView(activity); - emptyView.setText(activity.getText(R.string.producteev_no_dashboard)); - responsibleSelector.setEmptyView(emptyView); + responsibleSelector.setAdapter(null); view.findViewById(R.id.producteev_TEA_task_assign_label).setVisibility(View.GONE); return; } - // Fill the responsible-spinner and set the current responsible - users = ownerDashboard.getUsers(); - long responsibleId = metadata.getValue(ProducteevTask.RESPONSIBLE_ID); - int userSpinnerIndex = 0; - - for (ProducteevUser user : users) { - if (user.getId() == responsibleId) { - break; - } - userSpinnerIndex++; - } - ArrayAdapter usersAdapter = new ArrayAdapter(activity, - android.R.layout.simple_spinner_item, users); - usersAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - responsibleSelector.setAdapter(usersAdapter); - responsibleSelector.setSelection(userSpinnerIndex); + refreshResponsibleSpinner(ownerDashboard.getUsers()); } @Override diff --git a/astrid/res/values/strings-producteev.xml b/astrid/res/values/strings-producteev.xml index 6b368884e..8e68070c0 100644 --- a/astrid/res/values/strings-producteev.xml +++ b/astrid/res/values/strings-producteev.xml @@ -1,9 +1,9 @@ - - + + Producteev From 7b5a6ebae2cbe3296116dba76c49d69d1aede821 Mon Sep 17 00:00:00 2001 From: Arne Jans Date: Thu, 19 Aug 2010 04:01:52 +0200 Subject: [PATCH 04/17] dashboards can now be created and assigned from within astrid. setting responsible person is now sent to server, too (fixin missing parameter in invoker.tasksCreate) --- .../producteev/ProducteevControlSet.java | 84 +++++++++++++++++-- .../producteev/ProducteevUtilities.java | 3 + .../producteev/api/ProducteevInvoker.java | 14 +++- .../producteev/sync/ProducteevDashboard.java | 2 +- .../sync/ProducteevDataService.java | 70 +++++++++------- .../sync/ProducteevSyncProvider.java | 4 +- astrid/res/values/strings-producteev.xml | 9 ++ 7 files changed, 146 insertions(+), 40 deletions(-) diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevControlSet.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevControlSet.java index dc7cbd2a9..86e6de628 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevControlSet.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevControlSet.java @@ -2,27 +2,35 @@ package com.todoroo.astrid.producteev; import java.util.ArrayList; +import org.json.JSONObject; + import android.app.Activity; +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; +import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; +import android.widget.AdapterView.OnItemSelectedListener; import com.timsu.astrid.R; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.astrid.activity.TaskEditActivity.TaskEditControlSet; import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.StoreObject; import com.todoroo.astrid.model.Task; import com.todoroo.astrid.producteev.sync.ProducteevDashboard; import com.todoroo.astrid.producteev.sync.ProducteevDataService; +import com.todoroo.astrid.producteev.sync.ProducteevSyncProvider; import com.todoroo.astrid.producteev.sync.ProducteevTask; import com.todoroo.astrid.producteev.sync.ProducteevUser; import com.todoroo.astrid.service.MetadataService; @@ -38,6 +46,7 @@ public class ProducteevControlSet implements TaskEditControlSet { // --- instance variables private final Activity activity; + private final DialogUtilities dialogUtilites; private final View view; private Task myTask; @@ -54,6 +63,8 @@ public class ProducteevControlSet implements TaskEditControlSet { DependencyInjectionService.getInstance().inject(this); this.activity = activity; + this.dialogUtilites = new DialogUtilities(); + view = LayoutInflater.from(activity).inflate(R.layout.producteev_control, parent, true); this.responsibleSelector = (Spinner) activity.findViewById(R.id.producteev_TEA_task_assign); @@ -67,9 +78,65 @@ public class ProducteevControlSet implements TaskEditControlSet { @Override public void onItemSelected(AdapterView spinnerParent, View spinnerView, int position, long id) { - Spinner dashSelector = (Spinner) spinnerParent; + final Spinner dashSelector = (Spinner) spinnerParent; ProducteevDashboard dashboard = (ProducteevDashboard) dashSelector.getSelectedItem(); - refreshResponsibleSpinner(dashboard.getUsers()); + if (dashboard.getId() == ProducteevUtilities.DASHBOARD_CREATE) { + // let the user create a new dashboard + final EditText editor = new EditText(ProducteevControlSet.this.activity); + OnClickListener okListener = new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Activity context = ProducteevControlSet.this.activity; + String newDashboardName = editor.getText().toString(); + if (newDashboardName == null || newDashboardName.length() == 0) { + dialog.cancel(); + } else { + // create the real dashboard, select it in the spinner and refresh responsiblespinner + ProgressDialog progressDialog = null; + try { + progressDialog = dialogUtilites.progressDialog(context, + context.getString(R.string.DLG_wait)); + JSONObject newDashJSON = ProducteevSyncProvider.getInvoker().dashboardsCreate(newDashboardName).getJSONObject("dashboard"); + StoreObject local = ProducteevDataService.getInstance().updateDashboards(newDashJSON, true); + if (local != null) { + ProducteevDashboard newDashboard = new ProducteevDashboard(local); + ArrayAdapter adapter = (ArrayAdapter) dashSelector.getAdapter(); + adapter.insert(newDashboard, adapter.getCount()-1); + dashSelector.setSelection(adapter.getCount()-2); + refreshResponsibleSpinner(newDashboard.getUsers()); + dialogUtilites.dismissDialog(context, progressDialog); + } + } catch (Exception e) { + dialogUtilites.dismissDialog(context, progressDialog); + dialogUtilites.okDialog(context, + context.getString(R.string.DLG_error, e.getMessage()), + new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + e.printStackTrace(); + dashSelector.setSelection(0); + } + } + + } + }; + OnClickListener cancelListener = new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }; + dialogUtilites.viewDialog(ProducteevControlSet.this.activity, + ProducteevControlSet.this.activity.getString(R.string.producteev_create_dashboard_name), + editor, + okListener, + cancelListener); + } else { + refreshResponsibleSpinner(dashboard.getUsers()); + } } @Override @@ -140,8 +207,8 @@ public class ProducteevControlSet implements TaskEditControlSet { ProducteevDashboard ownerDashboard = null; int dashboardSpinnerIndex = -1; - //dashboard to not sync as first spinner-entry - for (int i=0;i dashAdapter = new ArrayAdapter(activity, android.R.layout.simple_spinner_item, dashboards); @@ -157,7 +228,8 @@ public class ProducteevControlSet implements TaskEditControlSet { dashboardSelector.setAdapter(dashAdapter); dashboardSelector.setSelection(dashboardSpinnerIndex+1); - if (ownerDashboard == null || ownerDashboard.getId() == ProducteevUtilities.DASHBOARD_NO_SYNC) { + if (ownerDashboard == null || ownerDashboard.getId() == ProducteevUtilities.DASHBOARD_NO_SYNC + || ownerDashboard.getId() == ProducteevUtilities.DASHBOARD_CREATE) { responsibleSelector.setEnabled(false); responsibleSelector.setAdapter(null); view.findViewById(R.id.producteev_TEA_task_assign_label).setVisibility(View.GONE); diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevUtilities.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevUtilities.java index 391e96ef7..f73a39823 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevUtilities.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevUtilities.java @@ -18,6 +18,9 @@ public class ProducteevUtilities extends SyncProviderUtilities { public static final ProducteevUtilities INSTANCE = new ProducteevUtilities(); + /** setting for dashboard to getting created */ + public static final int DASHBOARD_CREATE = -2; + /** setting for dashboard to not synchronize */ public static final int DASHBOARD_NO_SYNC = -1; diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevInvoker.java b/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevInvoker.java index d7d8899bd..1448456b9 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevInvoker.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevInvoker.java @@ -389,6 +389,18 @@ public class ProducteevInvoker { "id_colleague", idColleague); } + /** + * create a dasbhoard + * + * @param name + * @return the new created dashboard as JSONObject + */ + public JSONObject dashboardsCreate(String name) throws ApiServiceException, IOException { + return callAuthenticated("dashboards/create.json", + "token", token, + "title", name); + } + /** * return the list of users who can access a specific dashboard * @@ -396,7 +408,7 @@ public class ProducteevInvoker { * @param dashboard array-information about the dashboard, if this ... */ public JSONArray dashboardsAccess(long idDashboard, String dashboard) throws ApiServiceException, IOException { - return getResponse(callAuthenticated("dashboards/access", + return getResponse(callAuthenticated("dashboards/access.json", "token", token, "id_dashboard", idDashboard, "dashboard", dashboard),"dashboard"); diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDashboard.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDashboard.java index 2deefe8cf..5c763d3c3 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDashboard.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDashboard.java @@ -38,7 +38,7 @@ public class ProducteevDashboard { private ArrayList users = null; public ProducteevDashboard (StoreObject dashboardData) { - this(dashboardData.getValue(REMOTE_ID),dashboardData.getValue(NAME),dashboardData.getValue(USERS)); + this(dashboardData.getValue(REMOTE_ID),dashboardData.getValue(NAME),(dashboardData.containsValue(USERS)?dashboardData.getValue(USERS):null)); } /** diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDataService.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDataService.java index f089b0c4d..1577f65ab 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDataService.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDataService.java @@ -243,41 +243,49 @@ public final class ProducteevDataService { readDashboards(); for(int i = 0; i < changedDashboards.length(); i++) { JSONObject remote = changedDashboards.getJSONObject(i).getJSONObject("dashboard"); - long id = remote.getLong("id_dashboard"); - StoreObject local = null; - for(StoreObject dashboard : dashboards) { - if(dashboard.getValue(ProducteevDashboard.REMOTE_ID).equals(id)) { - local = dashboard; - break; - } - } + updateDashboards(remote, false); + } - if(remote.getInt("deleted") != 0) { - if(local != null) - storeObjectDao.delete(local.getId()); - continue; - } + // clear dashboard cache + dashboards = null; + } - if(local == null) - local = new StoreObject(); - local.setValue(StoreObject.TYPE, ProducteevDashboard.TYPE); - local.setValue(ProducteevDashboard.REMOTE_ID, id); - local.setValue(ProducteevDashboard.NAME, remote.getString("title")); - - StringBuilder users = new StringBuilder(); - JSONArray accessList = remote.getJSONArray("accesslist"); - for(int j = 0; j < accessList.length(); j++) { - JSONObject user = accessList.getJSONObject(j).getJSONObject("user"); - users.append(user.getLong("id_user")).append(','); - String name = user.optString("firstname", "") + ' ' + - user.optString("lastname", ""); - users.append(name.trim()).append(';'); + public StoreObject updateDashboards(JSONObject remote, boolean reinitCache) throws JSONException { + if (reinitCache) + readDashboards(); + long id = remote.getLong("id_dashboard"); + StoreObject local = null; + for(StoreObject dashboard : dashboards) { + if(dashboard.getValue(ProducteevDashboard.REMOTE_ID).equals(id)) { + local = dashboard; + break; } - local.setValue(ProducteevDashboard.USERS, users.toString()); - storeObjectDao.persist(local); } - // clear dashboard cache - dashboards = null; + if(remote.getInt("deleted") != 0) { + if(local != null) + storeObjectDao.delete(local.getId()); + } + + if(local == null) + local = new StoreObject(); + local.setValue(StoreObject.TYPE, ProducteevDashboard.TYPE); + local.setValue(ProducteevDashboard.REMOTE_ID, id); + local.setValue(ProducteevDashboard.NAME, remote.getString("title")); + + StringBuilder users = new StringBuilder(); + JSONArray accessList = remote.getJSONArray("accesslist"); + for(int j = 0; j < accessList.length(); j++) { + JSONObject user = accessList.getJSONObject(j).getJSONObject("user"); + users.append(user.getLong("id_user")).append(','); + String name = user.optString("firstname", "") + ' ' + + user.optString("lastname", ""); + users.append(name.trim()).append(';'); + } + local.setValue(ProducteevDashboard.USERS, users.toString()); + storeObjectDao.persist(local); + if (reinitCache) + dashboards = null; + return local; } } \ No newline at end of file diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java index 29dbdcd12..a1558ba1f 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java @@ -255,6 +255,7 @@ public class ProducteevSyncProvider extends SyncProvider Do Not Synchronize + + Add new Workspace... + + + Name for new Workspace + + + new + Default Workspace From 1049549629860c59d8aafe82e0735a50a32b7acb Mon Sep 17 00:00:00 2001 From: Tim Su Date: Sun, 22 Aug 2010 00:22:21 -0700 Subject: [PATCH 05/17] Shortened notification title --- astrid/res/values/strings-producteev.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astrid/res/values/strings-producteev.xml b/astrid/res/values/strings-producteev.xml index 31da06e84..8f643490f 100644 --- a/astrid/res/values/strings-producteev.xml +++ b/astrid/res/values/strings-producteev.xml @@ -93,7 +93,7 @@ - Astrid: Producteev + Producteev %s tasks updated / click for more details From adea1b31264cb13da3bca556a98a9f026f4fcfef Mon Sep 17 00:00:00 2001 From: Tim Su Date: Tue, 24 Aug 2010 02:02:33 -0700 Subject: [PATCH 06/17] AST-246 - caching task details --- astrid/AndroidManifest.xml | 38 ++- astrid/astrid.launch | 2 +- .../astrid/alarms/AlarmDetailExposer.java | 9 +- .../astrid/notes/NoteDetailExposer.java | 9 +- .../producteev/ProducteevDetailExposer.java | 9 +- .../astrid/repeats/RepeatDetailExposer.java | 4 +- .../astrid/rmilk/MilkDetailExposer.java | 9 +- .../todoroo/astrid/tags/TagDetailExposer.java | 9 +- astrid/res/layout/task_adapter_row.xml | 2 +- .../astrid/activity/TaskListActivity.java | 4 +- .../todoroo/astrid/adapter/TaskAdapter.java | 229 +++++++++++------- .../com/todoroo/astrid/api/DetailExposer.java | 26 -- .../src/com/todoroo/astrid/dao/Database.java | 6 +- .../src/com/todoroo/astrid/dao/TaskDao.java | 4 + astrid/src/com/todoroo/astrid/model/Task.java | 13 +- 15 files changed, 207 insertions(+), 166 deletions(-) delete mode 100644 astrid/src/com/todoroo/astrid/api/DetailExposer.java diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index 16bf4d4ec..0aca243eb 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -189,6 +189,12 @@ + + + + + + @@ -202,6 +208,12 @@ + + + + + + @@ -217,6 +229,12 @@ + + + + + + @@ -255,6 +273,12 @@ + + + + + + - + + + + + + + @@ -373,6 +403,12 @@ + + + + + + diff --git a/astrid/astrid.launch b/astrid/astrid.launch index 19f7df00c..89ae0a49d 100644 --- a/astrid/astrid.launch +++ b/astrid/astrid.launch @@ -12,7 +12,7 @@ - + diff --git a/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmDetailExposer.java index b8596da3b..4a25599e1 100644 --- a/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmDetailExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmDetailExposer.java @@ -12,7 +12,6 @@ import com.timsu.astrid.R; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.astrid.api.AstridApiConstants; -import com.todoroo.astrid.api.DetailExposer; import com.todoroo.astrid.model.Metadata; /** @@ -21,7 +20,7 @@ import com.todoroo.astrid.model.Metadata; * @author Tim Su * */ -public class AlarmDetailExposer extends BroadcastReceiver implements DetailExposer { +public class AlarmDetailExposer extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -44,7 +43,6 @@ public class AlarmDetailExposer extends BroadcastReceiver implements DetailExpos context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); } - @Override public String getTaskDetails(Context context, long id, boolean extended) { if(extended) return null; @@ -71,9 +69,4 @@ public class AlarmDetailExposer extends BroadcastReceiver implements DetailExpos } } - @Override - public String getPluginIdentifier() { - return AlarmService.IDENTIFIER; - } - } diff --git a/astrid/plugin-src/com/todoroo/astrid/notes/NoteDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/notes/NoteDetailExposer.java index ba9a879c0..29df48da9 100644 --- a/astrid/plugin-src/com/todoroo/astrid/notes/NoteDetailExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/notes/NoteDetailExposer.java @@ -10,7 +10,6 @@ import android.content.Intent; import com.timsu.astrid.R; import com.todoroo.astrid.api.AstridApiConstants; -import com.todoroo.astrid.api.DetailExposer; import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.model.Task; import com.todoroo.astrid.utility.Preferences; @@ -21,7 +20,7 @@ import com.todoroo.astrid.utility.Preferences; * @author Tim Su * */ -public class NoteDetailExposer extends BroadcastReceiver implements DetailExposer { +public class NoteDetailExposer extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -44,7 +43,6 @@ public class NoteDetailExposer extends BroadcastReceiver implements DetailExpose context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); } - @Override public String getTaskDetails(Context context, long id, boolean extended) { if(Preferences.getBoolean(R.string.p_showNotes, false)) { @@ -65,9 +63,4 @@ public class NoteDetailExposer extends BroadcastReceiver implements DetailExpose return " " + notes; //$NON-NLS-1$ } - @Override - public String getPluginIdentifier() { - return NotesPlugin.IDENTIFIER; - } - } diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevDetailExposer.java index 545a3f822..69f14ccfd 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevDetailExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevDetailExposer.java @@ -11,7 +11,6 @@ import com.timsu.astrid.R; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.astrid.adapter.TaskAdapter; import com.todoroo.astrid.api.AstridApiConstants; -import com.todoroo.astrid.api.DetailExposer; import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.StoreObject; import com.todoroo.astrid.producteev.sync.ProducteevDashboard; @@ -27,7 +26,7 @@ import com.todoroo.astrid.utility.Preferences; * @author Tim Su * */ -public class ProducteevDetailExposer extends BroadcastReceiver implements DetailExposer{ +public class ProducteevDetailExposer extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -52,7 +51,6 @@ public class ProducteevDetailExposer extends BroadcastReceiver implements Detail context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); } - @Override public String getTaskDetails(Context context, long id, boolean extended) { Metadata metadata = ProducteevDataService.getInstance().getTaskMetadata(id); if(metadata == null) @@ -133,9 +131,4 @@ public class ProducteevDetailExposer extends BroadcastReceiver implements Detail return null; } - @Override - public String getPluginIdentifier() { - return ProducteevUtilities.IDENTIFIER; - } - } diff --git a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java index e3cc35d97..9ae22f082 100644 --- a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java @@ -17,7 +17,6 @@ import com.google.ical.values.RRule; import com.google.ical.values.WeekdayNum; import com.timsu.astrid.R; import com.todoroo.astrid.api.AstridApiConstants; -import com.todoroo.astrid.api.DetailExposer; import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.model.Task; @@ -27,7 +26,7 @@ import com.todoroo.astrid.model.Task; * @author Tim Su * */ -public class RepeatDetailExposer extends BroadcastReceiver implements DetailExposer { +public class RepeatDetailExposer extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -119,7 +118,6 @@ public class RepeatDetailExposer extends BroadcastReceiver implements DetailExpo return null; } - @Override public String getPluginIdentifier() { return RepeatsPlugin.IDENTIFIER; } diff --git a/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkDetailExposer.java index 6f0eebdd4..4348f354d 100644 --- a/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkDetailExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkDetailExposer.java @@ -11,7 +11,6 @@ import com.timsu.astrid.R; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.astrid.adapter.TaskAdapter; import com.todoroo.astrid.api.AstridApiConstants; -import com.todoroo.astrid.api.DetailExposer; import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.rmilk.data.MilkDataService; import com.todoroo.astrid.rmilk.data.MilkNote; @@ -26,7 +25,7 @@ import com.todoroo.astrid.rmilk.data.MilkTask; * @author Tim Su * */ -public class MilkDetailExposer extends BroadcastReceiver implements DetailExposer{ +public class MilkDetailExposer extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -51,7 +50,6 @@ public class MilkDetailExposer extends BroadcastReceiver implements DetailExpose context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); } - @Override public String getTaskDetails(Context context, long id, boolean extended) { Metadata metadata = MilkDataService.getInstance().getTaskMetadata(id); if(metadata == null) @@ -92,9 +90,4 @@ public class MilkDetailExposer extends BroadcastReceiver implements DetailExpose return result.substring(0, result.length() - TaskAdapter.DETAIL_SEPARATOR.length()); } - @Override - public String getPluginIdentifier() { - return MilkUtilities.IDENTIFIER; - } - } diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java index 21172fe2e..a4f0b26fe 100644 --- a/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java @@ -8,7 +8,6 @@ import android.content.Context; import android.content.Intent; import com.todoroo.astrid.api.AstridApiConstants; -import com.todoroo.astrid.api.DetailExposer; /** * Exposes Task Detail for tags, i.e. "Tags: frogs, animals" @@ -16,7 +15,7 @@ import com.todoroo.astrid.api.DetailExposer; * @author Tim Su * */ -public class TagDetailExposer extends BroadcastReceiver implements DetailExposer { +public class TagDetailExposer extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -39,7 +38,6 @@ public class TagDetailExposer extends BroadcastReceiver implements DetailExposer context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); } - @Override public String getTaskDetails(Context context, long id, boolean extended) { if(extended) return null; @@ -51,9 +49,4 @@ public class TagDetailExposer extends BroadcastReceiver implements DetailExposer return " " + tagList; //$NON-NLS-1$ } - @Override - public String getPluginIdentifier() { - return TagsPlugin.IDENTIFIER; - } - } diff --git a/astrid/res/layout/task_adapter_row.xml b/astrid/res/layout/task_adapter_row.xml index 40c201cac..136776479 100644 --- a/astrid/res/layout/task_adapter_row.xml +++ b/astrid/res/layout/task_adapter_row.xml @@ -8,13 +8,13 @@ android:paddingBottom="4dip" android:paddingLeft="4dip" android:paddingRight="4dip" - android:minHeight="40dip" android:orientation="vertical"> diff --git a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java index a574089ca..50c8a6e1b 100644 --- a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java @@ -477,9 +477,9 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, } else if(AstridApiConstants.BROADCAST_SEND_DETAILS.equals(intent.getAction())) { String detail = extras.getString(AstridApiConstants.EXTRAS_RESPONSE); if(extras.getBoolean(AstridApiConstants.EXTRAS_EXTENDED)) - taskAdapter.detailManager.addNew(taskId, addOn, detail); - else taskAdapter.extendedDetailManager.addNew(taskId, addOn, detail); + else + taskAdapter.addDetails(taskId, detail); } else if(AstridApiConstants.BROADCAST_SEND_ACTIONS.equals(intent.getAction())) { TaskAction action = extras.getParcelable(AstridApiConstants.EXTRAS_RESPONSE); taskAdapter.taskActionManager.addNew(taskId, addOn, action); diff --git a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java index a36f16fab..dcd7ca948 100644 --- a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java +++ b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java @@ -1,6 +1,7 @@ package com.todoroo.astrid.adapter; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; @@ -15,6 +16,7 @@ import android.database.Cursor; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.text.Html; +import android.text.TextUtils; import android.text.Html.ImageGetter; import android.text.util.Linkify; import android.view.ContextMenu; @@ -43,18 +45,12 @@ import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.SoftHashMap; import com.todoroo.astrid.activity.TaskEditActivity; import com.todoroo.astrid.activity.TaskListActivity; -import com.todoroo.astrid.alarms.AlarmDetailExposer; import com.todoroo.astrid.api.AstridApiConstants; -import com.todoroo.astrid.api.DetailExposer; import com.todoroo.astrid.api.TaskAction; import com.todoroo.astrid.api.TaskDecoration; import com.todoroo.astrid.model.Task; -import com.todoroo.astrid.notes.NoteDetailExposer; -import com.todoroo.astrid.producteev.ProducteevDetailExposer; -import com.todoroo.astrid.repeats.RepeatDetailExposer; -import com.todoroo.astrid.rmilk.MilkDetailExposer; +import com.todoroo.astrid.service.AddOnService; import com.todoroo.astrid.service.TaskService; -import com.todoroo.astrid.tags.TagDetailExposer; import com.todoroo.astrid.utility.Constants; import com.todoroo.astrid.utility.Preferences; @@ -83,16 +79,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable { Task.COMPLETION_DATE, Task.HIDE_UNTIL, Task.DELETION_DATE, - }; - - /** Internal Task Detail exposers */ - public static final DetailExposer[] EXPOSERS = new DetailExposer[] { - new TagDetailExposer(), - new RepeatDetailExposer(), - new NoteDetailExposer(), - new MilkDetailExposer(), - new ProducteevDetailExposer(), - new AlarmDetailExposer(), + Task.DETAILS, }; private static int[] IMPORTANCE_COLORS = null; @@ -105,6 +92,9 @@ public class TaskAdapter extends CursorAdapter implements Filterable { @Autowired private TaskService taskService; + @Autowired + private AddOnService addOnService; + protected final ListActivity activity; protected final HashMap completedItems = new HashMap(); private OnCompletedTaskListener onCompletedTaskListener = null; @@ -112,6 +102,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable { private final int resource; private final LayoutInflater inflater; private int fontSize; + private DetailLoaderThread detailLoader; private final AtomicReference query; @@ -120,8 +111,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable { // --- task detail and decoration soft caches - public final DetailManager detailManager = new DetailManager(false); - public final DetailManager extendedDetailManager = new DetailManager(true); + public final DetailManager extendedDetailManager = new DetailManager(); public final DecorationManager decorationManager = new DecorationManager(); public final TaskActionManager taskActionManager = new TaskActionManager(); @@ -159,6 +149,9 @@ public class TaskAdapter extends CursorAdapter implements Filterable { if(IMPORTANCE_COLORS == null) IMPORTANCE_COLORS = Task.getImportanceColors(activity.getResources()); } + + detailLoader = new DetailLoaderThread(); + detailLoader.start(); } /* ====================================================================== @@ -221,6 +214,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable { ViewHolder viewHolder = ((ViewHolder)view.getTag()); Task task = viewHolder.task; + task.clear(); task.readFromCursor(cursor); setFieldContentsAndVisibility(view); @@ -324,9 +318,21 @@ public class TaskAdapter extends CursorAdapter implements Filterable { importanceView.setBackgroundColor(0); } + String details; + if(tasksToLoad.containsKey(task.getId())) + details = tasksToLoad.get(task.getId()).getValue(Task.DETAILS); + else + details = task.getValue(Task.DETAILS); + if(TextUtils.isEmpty(details)) { + viewHolder.details.setVisibility(View.GONE); + } else { + viewHolder.details.setVisibility(View.VISIBLE); + viewHolder.details.setText(Html.fromHtml(details.trim().replace("\n", //$NON-NLS-1$ + "
"), detailImageGetter, null)); //$NON-NLS-1$ + } + // details and decorations, expanded if(!isFling) { - detailManager.request(viewHolder); decorationManager.request(viewHolder); if(expanded == task.getId()) { extendedDetailManager.request(viewHolder); @@ -337,8 +343,9 @@ public class TaskAdapter extends CursorAdapter implements Filterable { } } else { long taskId = viewHolder.task.getId(); - detailManager.reset(viewHolder, taskId); decorationManager.reset(viewHolder, taskId); + viewHolder.extendedDetails.setVisibility(View.GONE); + viewHolder.actions.setVisibility(View.GONE); } } @@ -359,6 +366,81 @@ public class TaskAdapter extends CursorAdapter implements Filterable { container.setOnClickListener(listener); } + /* ====================================================================== + * ============================================================== details + * ====================================================================== */ + + private final Map tasksToLoad = Collections.synchronizedMap(new HashMap()); + + public class DetailLoaderThread extends Thread { + @Override + public void run() { + // for all of the tasks returned by our cursor, verify details + TodorooCursor fetchCursor = taskService.fetchFiltered( + query.get(), null, Task.ID, Task.DETAILS); + activity.startManagingCursor(fetchCursor); + Task task = new Task(); + for(fetchCursor.moveToFirst(); !fetchCursor.isAfterLast(); fetchCursor.moveToNext()) { + task.clear(); + task.readFromCursor(fetchCursor); + if(!task.containsNonNullValue(Task.DETAILS)) { + System.err.println("READING details for " + task.getId()); + + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DETAILS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId()); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, false); + activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + + Task loadHolder = new Task(fetchCursor); + tasksToLoad.put(task.getId(), loadHolder); + + task.setValue(Task.DETAILS, ""); //$NON-NLS-1$ + taskService.save(task); + } + } + } + } + + /** + * Add detail to a task + * + * @param id + * @param detail + */ + public void addDetails(long id, String detail) { + final Task task = tasksToLoad.get(id); + if(task == null) + return; + String newDetails = task.getValue(Task.DETAILS); + if(TextUtils.isEmpty(newDetails)) + newDetails = detail; + else if(newDetails.contains(detail)) + return; + else + newDetails += DETAIL_SEPARATOR + detail; + task.setValue(Task.DETAILS, newDetails); + taskService.save(task); + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + notifyDataSetInvalidated(); + } + }); + } + + private final ImageGetter detailImageGetter = new ImageGetter() { + public Drawable getDrawable(String source) { + Resources r = activity.getResources(); + int drawable = r.getIdentifier("drawable/" + source, null, Constants.PACKAGE); //$NON-NLS-1$ + if(drawable == 0) + return null; + Drawable d = r.getDrawable(drawable); + d.setBounds(0,0,d.getIntrinsicWidth(),d.getIntrinsicHeight()); + return d; + } + }; + /* ====================================================================== * ============================================================== add-ons * ====================================================================== */ @@ -367,10 +449,11 @@ public class TaskAdapter extends CursorAdapter implements Filterable { * Called to tell the cache to be cleared */ public void flushCaches() { - detailManager.clearCache(); extendedDetailManager.clearCache(); decorationManager.clearCache(); taskActionManager.clearCache(); + detailLoader = new DetailLoaderThread(); + detailLoader.start(); } /** @@ -380,62 +463,21 @@ public class TaskAdapter extends CursorAdapter implements Filterable { */ public class DetailManager extends AddOnManager { - private final ImageGetter imageGetter = new ImageGetter() { - public Drawable getDrawable(String source) { - Resources r = activity.getResources(); - int drawable = r.getIdentifier("drawable/" + source, null, Constants.PACKAGE); //$NON-NLS-1$ - if(drawable == 0) - return null; - Drawable d = r.getDrawable(drawable); - d.setBounds(0,0,d.getIntrinsicWidth(),d.getIntrinsicHeight()); - return d; - } - }; - - private final boolean extended; - public DetailManager(boolean extended) { - this.extended = extended; + public DetailManager() { + // } @Override Intent createBroadcastIntent(long taskId) { Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DETAILS); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, extended); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, true); return broadcastIntent; } @Override - public boolean request(final ViewHolder viewHolder) { - if(super.request(viewHolder)) { - final long taskId = viewHolder.task.getId(); - // load internal details - new Thread() { - @Override - public void run() { - for(DetailExposer exposer : EXPOSERS) { - final String detail = exposer.getTaskDetails(activity, - taskId, extended); - if(detail == null) - continue; - final Collection cacheList = - addIfNotExists(taskId, exposer.getPluginIdentifier(), - detail); - if(cacheList != null) { - if(taskId != viewHolder.task.getId()) - continue; - activity.runOnUiThread(new Runnable() { - public void run() { - draw(viewHolder, taskId, cacheList); - } - }); - } - } - }; - }.start(); - return true; - } - return false; + public void addNew(long taskId, String addOn, String item) { + super.addNew(taskId, addOn, item); } @SuppressWarnings("nls") @@ -443,9 +485,9 @@ public class TaskAdapter extends CursorAdapter implements Filterable { void draw(ViewHolder viewHolder, long taskId, Collection details) { if(details == null || viewHolder.task.getId() != taskId) return; - TextView view = extended ? viewHolder.extendedDetails : viewHolder.details; - reset(viewHolder, taskId); - if(details.isEmpty() || (extended && expanded != taskId)) { + TextView view = viewHolder.extendedDetails; + if(details.isEmpty() || (expanded != taskId)) { + reset(viewHolder, taskId); return; } view.setVisibility(View.VISIBLE); @@ -457,7 +499,8 @@ public class TaskAdapter extends CursorAdapter implements Filterable { } String string = detailText.toString(); if(string.contains("<")) - view.setText(Html.fromHtml(string.trim().replace("\n", "
"), imageGetter, null)); + view.setText(Html.fromHtml(string.trim().replace("\n", "
"), + detailImageGetter, null)); else view.setText(string.trim()); Linkify.addLinks(view, Linkify.ALL); @@ -465,7 +508,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable { @Override void reset(ViewHolder viewHolder, long taskId) { - TextView view = extended ? viewHolder.extendedDetails : viewHolder.details; + TextView view = viewHolder.extendedDetails; view.setVisibility(View.GONE); } } @@ -533,6 +576,11 @@ public class TaskAdapter extends CursorAdapter implements Filterable { * */ public class TaskActionManager extends AddOnManager { + + private final LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, + LayoutParams.FILL_PARENT, 1f); + @Override Intent createBroadcastIntent(long taskId) { Intent intent = new Intent(AstridApiConstants.BROADCAST_REQUEST_ACTIONS); @@ -545,15 +593,24 @@ public class TaskAdapter extends CursorAdapter implements Filterable { if(actions == null || viewHolder.task.getId() != taskId) return; - reset(viewHolder, taskId); + // hack because we know we have > 1 button + if(addOnService.hasPowerPack() && actions.size() == 0) + return; - LinearLayout.LayoutParams params = - new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, - LayoutParams.FILL_PARENT, 1f); + for(int i = viewHolder.actions.getChildCount(); i < actions.size() + 1; i++) { + Button editButton = new Button(activity); + editButton.setLayoutParams(params); + viewHolder.actions.addView(editButton); + } + if(actions.size() + 1 < viewHolder.actions.getChildCount()) + viewHolder.actions.removeViews(0, viewHolder.actions.getChildCount() - + actions.size() - 1); - Button editButton = new Button(activity); - editButton.setText(R.string.TAd_actionEditTask); - editButton.setOnClickListener(new OnClickListener() { + int i = 0; + Button button = (Button) viewHolder.actions.getChildAt(i++); + + button.setText(R.string.TAd_actionEditTask); + button.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { Intent intent = new Intent(activity, TaskEditActivity.class); @@ -561,17 +618,14 @@ public class TaskAdapter extends CursorAdapter implements Filterable { activity.startActivityForResult(intent, TaskListActivity.ACTIVITY_EDIT_TASK); } }); - editButton.setLayoutParams(params); - viewHolder.actions.addView(editButton); for(TaskAction action : actions) { - Button view = new Button(activity); - view.setText(action.text); - view.setOnClickListener(new ActionClickListener(action)); - view.setLayoutParams(params); - viewHolder.actions.addView(view); + button = (Button) viewHolder.actions.getChildAt(i++); + button.setText(action.text); + button.setOnClickListener(new ActionClickListener(action)); } + reset(viewHolder, taskId); } @Override @@ -581,7 +635,6 @@ public class TaskAdapter extends CursorAdapter implements Filterable { return; } viewHolder.actions.setVisibility(View.VISIBLE); - viewHolder.actions.removeAllViews(); } } diff --git a/astrid/src/com/todoroo/astrid/api/DetailExposer.java b/astrid/src/com/todoroo/astrid/api/DetailExposer.java deleted file mode 100644 index 90ef95b9d..000000000 --- a/astrid/src/com/todoroo/astrid/api/DetailExposer.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.todoroo.astrid.api; - -import android.content.Context; - -/** - * Internal API for Task Details - * - * @author Tim Su - * - */ -public interface DetailExposer { - - /** - * @param id - * task id - * @param extended - * whether this request is for extended details (which are - * displayed when user presses a task), or standard (which are - * always displayed) - * @return null if no details, or task details - */ - public String getTaskDetails(Context context, long id, boolean extended); - - public String getPluginIdentifier(); - -} diff --git a/astrid/src/com/todoroo/astrid/dao/Database.java b/astrid/src/com/todoroo/astrid/dao/Database.java index 6bef32257..949845695 100644 --- a/astrid/src/com/todoroo/astrid/dao/Database.java +++ b/astrid/src/com/todoroo/astrid/dao/Database.java @@ -28,7 +28,7 @@ public class Database extends AbstractDatabase { * Database version number. This variable must be updated when database * tables are updated, as it determines whether a database needs updating. */ - public static final int VERSION = 4; + public static final int VERSION = 5; /** * Database name (must be unique) @@ -119,6 +119,10 @@ public class Database extends AbstractDatabase { append(')'); database.execSQL(sql.toString()); } + case 4: { + database.execSQL("ALTER TABLE " + Task.TABLE.name + " ADD " + + Task.DETAILS.accept(visitor, null)); + } return true; } diff --git a/astrid/src/com/todoroo/astrid/dao/TaskDao.java b/astrid/src/com/todoroo/astrid/dao/TaskDao.java index 6298f9af6..f55ba4c6a 100644 --- a/astrid/src/com/todoroo/astrid/dao/TaskDao.java +++ b/astrid/src/com/todoroo/astrid/dao/TaskDao.java @@ -157,6 +157,10 @@ public class TaskDao extends GenericDao { return false; } + // clear task detail cache + if(values != null && !values.containsKey(Task.DETAILS.name)) + values.put(Task.DETAILS.name, (String)null); + if (task.getId() == Task.NO_ID) { saveSuccessful = createNew(task); } else { diff --git a/astrid/src/com/todoroo/astrid/model/Task.java b/astrid/src/com/todoroo/astrid/model/Task.java index b0130614d..aa81af7f8 100644 --- a/astrid/src/com/todoroo/astrid/model/Task.java +++ b/astrid/src/com/todoroo/astrid/model/Task.java @@ -14,11 +14,11 @@ import android.content.res.Resources; import com.timsu.astrid.R; import com.todoroo.andlib.data.AbstractModel; import com.todoroo.andlib.data.Property; +import com.todoroo.andlib.data.Table; +import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.data.Property.IntegerProperty; import com.todoroo.andlib.data.Property.LongProperty; import com.todoroo.andlib.data.Property.StringProperty; -import com.todoroo.andlib.data.Table; -import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.utility.DateUtilities; /** @@ -72,7 +72,13 @@ public final class Task extends AbstractModel { public static final LongProperty DELETION_DATE = new LongProperty( TABLE, "deleted"); - // --- for migration purposes from astrid 2 (eventually we will want to + /** Cached Details Column - built from add-on detail exposers. A null + * value means there is no value in the cache and it needs to be + * refreshed */ + public static final StringProperty DETAILS = new StringProperty( + TABLE, "details"); + + // --- for migration purposes from astrid 2 (eventually we may want to // move these into the metadata table and treat them as plug-ins public static final StringProperty NOTES = new StringProperty( @@ -179,6 +185,7 @@ public final class Task extends AbstractModel { defaultValues.put(NOTES.name, ""); defaultValues.put(FLAGS.name, 0); defaultValues.put(TIMER_START.name, 0); + defaultValues.put(DETAILS.name, (String)null); } @Override From fe631ac97e3bb657c51e5c990bc980d350db8f5a Mon Sep 17 00:00:00 2001 From: Tim Su Date: Tue, 24 Aug 2010 02:06:43 -0700 Subject: [PATCH 07/17] Better handling of tapping on actual title not body --- astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java index dcd7ca948..b1258d8b9 100644 --- a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java +++ b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java @@ -355,15 +355,18 @@ public class TaskAdapter extends CursorAdapter implements Filterable { * created. */ private void addListeners(final View container) { + ViewHolder viewHolder = (ViewHolder)container.getTag(); + // check box listener - final CheckBox completeBox = ((CheckBox)container.findViewById(R.id.completeBox)); - completeBox.setOnClickListener(completeBoxListener); + viewHolder.completeBox.setOnClickListener(completeBoxListener); // context menu listener container.setOnCreateContextMenuListener(listener); // tap listener container.setOnClickListener(listener); + viewHolder.nameView.setTag(viewHolder); + viewHolder.nameView.setOnClickListener(listener); } /* ====================================================================== From edb6379c9f2bcb6f05d58ac1e42f867c98d32fb6 Mon Sep 17 00:00:00 2001 From: Tim Su Date: Tue, 24 Aug 2010 02:08:20 -0700 Subject: [PATCH 08/17] Added a purge item --- astrid/res/values/strings-core.xml | 3 +++ .../todoroo/astrid/activity/TaskListActivity.java | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/astrid/res/values/strings-core.xml b/astrid/res/values/strings-core.xml index 9dcf07f73..321558cbe 100644 --- a/astrid/res/values/strings-core.xml +++ b/astrid/res/values/strings-core.xml @@ -214,6 +214,9 @@ button: add task & go to the edit page. Undelete Task + + + Purge Task diff --git a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java index 50c8a6e1b..b5d2ee2c5 100644 --- a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java @@ -116,7 +116,8 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, private static final int CONTEXT_MENU_EDIT_TASK_ID = Menu.FIRST + 6; private static final int CONTEXT_MENU_DELETE_TASK_ID = Menu.FIRST + 7; private static final int CONTEXT_MENU_UNDELETE_TASK_ID = Menu.FIRST + 8; - private static final int CONTEXT_MENU_ADDON_INTENT_ID = Menu.FIRST + 9; + private static final int CONTEXT_MENU_PURGE_TASK_ID = Menu.FIRST + 9; + private static final int CONTEXT_MENU_ADDON_INTENT_ID = Menu.FIRST + 10; /** menu code indicating the end of the context menu */ private static final int CONTEXT_MENU_DEBUG = Menu.FIRST + 10; @@ -732,6 +733,9 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, if(task.isDeleted()) { menu.add(id, CONTEXT_MENU_UNDELETE_TASK_ID, Menu.NONE, R.string.TAd_contextUndeleteTask); + + menu.add(id, CONTEXT_MENU_PURGE_TASK_ID, Menu.NONE, + R.string.TAd_contextPurgeTask); } else { menu.add(id, CONTEXT_MENU_EDIT_TASK_ID, Menu.NONE, R.string.TAd_contextEditTask); @@ -841,6 +845,15 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, return true; } + case CONTEXT_MENU_PURGE_TASK_ID: { + itemId = item.getGroupId(); + Task task = new Task(); + task.setId(itemId); + taskService.delete(task); + loadTaskListContent(true); + return true; + } + // --- debug case CONTEXT_MENU_DEBUG: { From 4253cec645bbecd864f660ba1721909a5797bcd7 Mon Sep 17 00:00:00 2001 From: Tim Su Date: Tue, 24 Aug 2010 02:20:23 -0700 Subject: [PATCH 09/17] Clearing cache when logging out of PDV, escaping producteev activities and workspaces, fixing warnings --- .../com/todoroo/andlib/data/GenericDao.java | 15 +++++++++ .../sync/ProducteevDataService.java | 14 ++++++--- .../sync/ProducteevSyncProvider.java | 31 +++++++------------ .../astrid/activity/TaskListActivity.java | 2 +- .../todoroo/astrid/adapter/TaskAdapter.java | 5 +-- .../todoroo/astrid/service/TaskService.java | 14 ++++++++- 6 files changed, 53 insertions(+), 28 deletions(-) diff --git a/astrid/common-src/com/todoroo/andlib/data/GenericDao.java b/astrid/common-src/com/todoroo/andlib/data/GenericDao.java index 7c80b6a4f..1da5f3021 100644 --- a/astrid/common-src/com/todoroo/andlib/data/GenericDao.java +++ b/astrid/common-src/com/todoroo/andlib/data/GenericDao.java @@ -196,6 +196,21 @@ public class GenericDao { AbstractModel.ID_PROPERTY.eq(item.getId()).toString(), null) > 0; } + /** + * Updates multiple rows of the database based on model set values + * + * @param item + * item model + * @param criterion + * @return returns true on success. + */ + public int updateMultiple(TYPE item, Criterion criterion) { + ContentValues values = item.getSetValues(); + if(values.size() == 0) // nothing changed + return 0; + return database.update(table.name, values, criterion.toString(), null); + } + // --- helper methods diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDataService.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDataService.java index 429873d50..19ca28fec 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDataService.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDataService.java @@ -20,15 +20,17 @@ import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.sql.Criterion; import com.todoroo.andlib.sql.Join; import com.todoroo.andlib.sql.Query; -import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; +import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.dao.StoreObjectDao; -import com.todoroo.astrid.dao.StoreObjectDao.StoreObjectCriteria; import com.todoroo.astrid.dao.TaskDao; +import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; +import com.todoroo.astrid.dao.StoreObjectDao.StoreObjectCriteria; import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.StoreObject; import com.todoroo.astrid.model.Task; import com.todoroo.astrid.producteev.ProducteevUtilities; +import com.todoroo.astrid.producteev.api.ApiUtilities; import com.todoroo.astrid.rmilk.data.MilkNote; import com.todoroo.astrid.service.MetadataService; import com.todoroo.astrid.tags.TagService; @@ -81,6 +83,7 @@ public final class ProducteevDataService { metadataService.deleteWhere(Metadata.KEY.eq(ProducteevTask.METADATA_KEY)); metadataService.deleteWhere(Metadata.KEY.eq(ProducteevNote.METADATA_KEY)); storeObjectDao.deleteWhere(StoreObject.TYPE.eq(ProducteevDashboard.TYPE)); + PluginServices.getTaskService().clearDetails(); } /** @@ -250,6 +253,7 @@ public final class ProducteevDataService { dashboards = null; } + @SuppressWarnings("nls") public StoreObject updateDashboards(JSONObject remote, boolean reinitCache) throws JSONException { if (reinitCache) readDashboards(); @@ -271,15 +275,15 @@ public final class ProducteevDataService { local = new StoreObject(); local.setValue(StoreObject.TYPE, ProducteevDashboard.TYPE); local.setValue(ProducteevDashboard.REMOTE_ID, id); - local.setValue(ProducteevDashboard.NAME, remote.getString("title")); + local.setValue(ProducteevDashboard.NAME, ApiUtilities.decode(remote.getString("title"))); StringBuilder users = new StringBuilder(); JSONArray accessList = remote.getJSONArray("accesslist"); for(int j = 0; j < accessList.length(); j++) { JSONObject user = accessList.getJSONObject(j).getJSONObject("user"); users.append(user.getLong("id_user")).append(','); - String name = user.optString("firstname", "") + ' ' + - user.optString("lastname", ""); + String name = ApiUtilities.decode(user.optString("firstname", "") + ' ' + + user.optString("lastname", "")); users.append(name.trim()).append(';'); } local.setValue(ProducteevDashboard.USERS, users.toString()); diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java index b4d7f5834..85ff5a547 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java @@ -259,24 +259,16 @@ public class ProducteevSyncProvider extends SyncProvider notificationsList = parseActivities(notifications); + String[] notificationsList = parseActivities(notifications); // update lastIds if (notifications.length() > 0) { lastNotificationId = ""+notifications.getJSONObject(0).getJSONObject("activity").getLong("id_activity"); } -// JSONArray activities = invoker.activitiesShowActivities(null, (lastActivityId == 0 ? null : new Long(lastActivityId))); -// int activitiesCount = (activities == null ? 0 : activities.length()); -// ArrayList activitiesList = parseActivities(activities); -// // update lastIds -// if (activities.length() > 0) { -// lastActivityId = activities.getJSONObject(0).getJSONObject("activity").getLong("id_activity"); -// } - // display notifications from producteev-log Context context = ContextManager.getContext(); final NotificationManager nm = new NotificationManager.AndroidNotificationManager(context); - for (int i = 0; i< notificationsList.size(); i++) { + for (int i = 0; i< notificationsList.length; i++) { long id_dashboard = notifications.getJSONObject(i).getJSONObject("activity").getLong("id_dashboard"); String dashboardName = null; StoreObject[] dashboardsData = ProducteevDataService.getInstance().getDashboards(); @@ -298,13 +290,12 @@ public class ProducteevSyncProvider extends SyncProvider]+>", ""); + String message = notificationsList[i].replaceAll("<[^>]+>", ""); PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0); notification.setLatestEventInfo(context, contentTitle, message, contentIntent); @@ -325,16 +316,18 @@ public class ProducteevSyncProvider extends SyncProvider Date: Tue, 24 Aug 2010 02:44:43 -0700 Subject: [PATCH 10/17] Improved the way that task details work (to use a string builder), nice background for selected tasks, make purge really work) --- .../astrid/activity/TaskListActivity.java | 4 +- .../todoroo/astrid/adapter/TaskAdapter.java | 50 ++++++++++++------- .../todoroo/astrid/service/TaskService.java | 9 ++++ 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java index 08f8b3d65..bead0446d 100644 --- a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java @@ -847,9 +847,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, case CONTEXT_MENU_PURGE_TASK_ID: { itemId = item.getGroupId(); - Task task = new Task(); - task.setId(itemId); - taskService.delete(task); + taskService.purge(itemId); loadTaskListContent(true); return true; } diff --git a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java index b732c823a..7d0163638 100644 --- a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java +++ b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java @@ -13,6 +13,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.text.Html; @@ -319,8 +320,8 @@ public class TaskAdapter extends CursorAdapter implements Filterable { } String details; - if(tasksToLoad.containsKey(task.getId())) - details = tasksToLoad.get(task.getId()).getValue(Task.DETAILS); + if(taskDetailLoader.containsKey(task.getId())) + details = taskDetailLoader.get(task.getId()).toString(); else details = task.getValue(Task.DETAILS); if(TextUtils.isEmpty(details)) { @@ -374,19 +375,26 @@ public class TaskAdapter extends CursorAdapter implements Filterable { * ============================================================== details * ====================================================================== */ - private final Map tasksToLoad = Collections.synchronizedMap(new HashMap()); + // implementation note: this map is really costly if users have + // a large number of tasks to load, since it all goes into memory. + // it's best to do this, though, in order to append details to each other + private final Map taskDetailLoader = Collections.synchronizedMap(new HashMap()); + + private final Task taskDetailContainer = new Task(); public class DetailLoaderThread extends Thread { @Override public void run() { // for all of the tasks returned by our cursor, verify details TodorooCursor fetchCursor = taskService.fetchFiltered( - query.get(), null, Task.ID, Task.DETAILS); + query.get(), null, Task.ID, Task.DETAILS, Task.COMPLETION_DATE); activity.startManagingCursor(fetchCursor); Task task = new Task(); for(fetchCursor.moveToFirst(); !fetchCursor.isAfterLast(); fetchCursor.moveToNext()) { task.clear(); task.readFromCursor(fetchCursor); + if(task.isCompleted()) + continue; if(!task.containsNonNullValue(Task.DETAILS)) { System.err.println("READING details for " + task.getId()); @@ -395,8 +403,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable { broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, false); activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); - Task loadHolder = new Task(fetchCursor); - tasksToLoad.put(task.getId(), loadHolder); + taskDetailLoader.put(task.getId(), new StringBuilder()); task.setValue(Task.DETAILS, ""); //$NON-NLS-1$ taskService.save(task); @@ -412,23 +419,27 @@ public class TaskAdapter extends CursorAdapter implements Filterable { * @param detail */ public void addDetails(long id, String detail) { - final Task task = tasksToLoad.get(id); - if(task == null) + final StringBuilder details = taskDetailLoader.get(id); + if(details == null) return; - String newDetails = task.getValue(Task.DETAILS); - if(TextUtils.isEmpty(newDetails)) - newDetails = detail; - else if(newDetails.contains(detail)) - return; - else - newDetails += DETAIL_SEPARATOR + detail; - task.setValue(Task.DETAILS, newDetails); - taskService.save(task); + synchronized(details) { + if(details.toString().contains(detail)) + return; + if(details.length() > 0) + details.append(DETAIL_SEPARATOR); + details.append(detail); + taskDetailContainer.setId(id); + taskDetailContainer.setValue(Task.DETAILS, details.toString()); + taskService.save(taskDetailContainer); + } activity.runOnUiThread(new Runnable() { @Override public void run() { + ListView listView = activity.getListView(); + int scrollPos = listView.getScrollY(); notifyDataSetInvalidated(); + listView.scrollTo(0, scrollPos); } }); } @@ -569,7 +580,10 @@ public class TaskAdapter extends CursorAdapter implements Filterable { for(View view : viewHolder.decorations) viewHolder.taskRow.removeView(view); } - viewHolder.view.setBackgroundResource(android.R.drawable.list_selector_background); + if(taskId == expanded) + viewHolder.view.setBackgroundColor(Color.argb(20, 255, 255, 255)); + else + viewHolder.view.setBackgroundResource(android.R.drawable.list_selector_background); } } diff --git a/astrid/src/com/todoroo/astrid/service/TaskService.java b/astrid/src/com/todoroo/astrid/service/TaskService.java index a6e7ccd9a..bb3accb3e 100644 --- a/astrid/src/com/todoroo/astrid/service/TaskService.java +++ b/astrid/src/com/todoroo/astrid/service/TaskService.java @@ -132,6 +132,15 @@ public class TaskService { } } + /** + * Permanently delete the given task. + * + * @param model + */ + public void purge(long taskId) { + taskDao.delete(taskId); + } + /** * Clean up tasks. Typically called on startup */ From 1cb46c83fc7681e65e420a3934ba732b68fcf0a8 Mon Sep 17 00:00:00 2001 From: Tim Su Date: Tue, 24 Aug 2010 02:48:31 -0700 Subject: [PATCH 11/17] Add a bit of padding, got rid of logging statement --- astrid/res/layout/task_adapter_row.xml | 2 ++ astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/astrid/res/layout/task_adapter_row.xml b/astrid/res/layout/task_adapter_row.xml index 136776479..751c74b8a 100644 --- a/astrid/res/layout/task_adapter_row.xml +++ b/astrid/res/layout/task_adapter_row.xml @@ -62,6 +62,7 @@ diff --git a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java index 7d0163638..075c40ca7 100644 --- a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java +++ b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java @@ -396,8 +396,6 @@ public class TaskAdapter extends CursorAdapter implements Filterable { if(task.isCompleted()) continue; if(!task.containsNonNullValue(Task.DETAILS)) { - System.err.println("READING details for " + task.getId()); - Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DETAILS); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId()); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, false); From e6bce4d275632a4c06f6455467cb1c502a7ec4e4 Mon Sep 17 00:00:00 2001 From: Tim Su Date: Tue, 24 Aug 2010 03:00:13 -0700 Subject: [PATCH 12/17] Added allDay flag for Producteev setDeadline, fixed unsetting deadline --- .../todoroo/astrid/producteev/api/ProducteevInvoker.java | 6 ++++-- .../astrid/producteev/sync/ProducteevSyncProvider.java | 4 ++-- astrid/src/com/todoroo/astrid/model/Task.java | 2 ++ astrid/src/com/todoroo/astrid/service/TaskService.java | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevInvoker.java b/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevInvoker.java index 9d2995f40..349ca292a 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevInvoker.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevInvoker.java @@ -242,14 +242,16 @@ public class ProducteevInvoker { * * @param idTask * @param deadline + * @param allDay (optional), 1: all day task (time specified in deadline will be ignored), 0: deadline with time * * @return array tasks/view */ - public JSONObject tasksSetDeadline(long idTask, String deadline) throws ApiServiceException, IOException { + public JSONObject tasksSetDeadline(long idTask, String deadline, Integer allDay) throws ApiServiceException, IOException { return callAuthenticated("tasks/set_deadline.json", "token", token, "id_task", idTask, - "deadline", deadline); + "deadline", deadline, + "all_day", allDay); } /** diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java index 85ff5a547..be46f3ef5 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java @@ -519,7 +519,7 @@ public class ProducteevSyncProvider extends SyncProvider Date: Tue, 24 Aug 2010 03:11:20 -0700 Subject: [PATCH 13/17] temporarily disable un-setting due date, squash another source of duplicate tasks - remotely new and completed tasks --- .../plugin-src/com/todoroo/astrid/common/SyncProvider.java | 6 ++++++ .../astrid/producteev/sync/ProducteevSyncProvider.java | 7 ++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/astrid/plugin-src/com/todoroo/astrid/common/SyncProvider.java b/astrid/plugin-src/com/todoroo/astrid/common/SyncProvider.java index 11477a6b0..0fd852c9a 100644 --- a/astrid/plugin-src/com/todoroo/astrid/common/SyncProvider.java +++ b/astrid/plugin-src/com/todoroo/astrid/common/SyncProvider.java @@ -304,6 +304,12 @@ public abstract class SyncProvider { length = data.remoteUpdated.size(); for(int i = 0; i < length; i++) { TYPE remote = data.remoteUpdated.get(i); + + // don't synchronize new & deleted / completed tasks + if(!remote.task.isSaved() && (remote.task.isDeleted() || + remote.task.isCompleted())) + continue; + try { write(remote); } catch (Exception e) { diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java index be46f3ef5..b63303d11 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java @@ -518,7 +518,7 @@ public class ProducteevSyncProvider extends SyncProvider Date: Tue, 24 Aug 2010 03:13:08 -0700 Subject: [PATCH 14/17] Still more shorten the notification title --- .../todoroo/astrid/producteev/sync/ProducteevSyncProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java index b63303d11..00b58247a 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java @@ -289,7 +289,7 @@ public class ProducteevSyncProvider extends SyncProvider Date: Tue, 24 Aug 2010 03:49:46 -0700 Subject: [PATCH 15/17] Fixed various bugs with clearing details, now try-catching detail loader, all kinds of goodies --- .../com/todoroo/andlib/data/GenericDao.java | 3 +- .../sync/ProducteevSyncProvider.java | 5 +- .../astrid/activity/EditPreferences.java | 15 +++++- .../todoroo/astrid/adapter/TaskAdapter.java | 46 ++++++++++--------- .../src/com/todoroo/astrid/dao/TaskDao.java | 2 +- .../todoroo/astrid/service/TaskService.java | 8 ++-- 6 files changed, 49 insertions(+), 30 deletions(-) diff --git a/astrid/common-src/com/todoroo/andlib/data/GenericDao.java b/astrid/common-src/com/todoroo/andlib/data/GenericDao.java index 1da5f3021..da3a59c22 100644 --- a/astrid/common-src/com/todoroo/andlib/data/GenericDao.java +++ b/astrid/common-src/com/todoroo/andlib/data/GenericDao.java @@ -204,8 +204,7 @@ public class GenericDao { * @param criterion * @return returns true on success. */ - public int updateMultiple(TYPE item, Criterion criterion) { - ContentValues values = item.getSetValues(); + public int updateMultiple(ContentValues values, Criterion criterion) { if(values.size() == 0) // nothing changed return 0; return database.update(table.name, values, criterion.toString(), null); diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java index 00b58247a..312573f1e 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java @@ -424,7 +424,10 @@ public class ProducteevSyncProvider extends SyncProvider DateUtilities.now()) nameValue = r.getString(R.string.TAd_hiddenFormat, nameValue); nameView.setText(nameValue); + viewHolder.nameView.setFocusable(false); + viewHolder.nameView.setClickable(false); Linkify.addLinks(nameView, Linkify.ALL); } @@ -324,7 +325,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable { details = taskDetailLoader.get(task.getId()).toString(); else details = task.getValue(Task.DETAILS); - if(TextUtils.isEmpty(details)) { + if(TextUtils.isEmpty(details) || DETAIL_SEPARATOR.equals(details)) { viewHolder.details.setVisibility(View.GONE); } else { viewHolder.details.setVisibility(View.VISIBLE); @@ -366,9 +367,6 @@ public class TaskAdapter extends CursorAdapter implements Filterable { // tap listener container.setOnClickListener(listener); - - viewHolder.nameView.setFocusable(false); - viewHolder.nameView.setClickable(false); } /* ====================================================================== @@ -389,23 +387,27 @@ public class TaskAdapter extends CursorAdapter implements Filterable { TodorooCursor fetchCursor = taskService.fetchFiltered( query.get(), null, Task.ID, Task.DETAILS, Task.COMPLETION_DATE); activity.startManagingCursor(fetchCursor); - Task task = new Task(); - for(fetchCursor.moveToFirst(); !fetchCursor.isAfterLast(); fetchCursor.moveToNext()) { - task.clear(); - task.readFromCursor(fetchCursor); - if(task.isCompleted()) - continue; - if(!task.containsNonNullValue(Task.DETAILS)) { - Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DETAILS); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId()); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, false); - activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); - - taskDetailLoader.put(task.getId(), new StringBuilder()); - - task.setValue(Task.DETAILS, ""); //$NON-NLS-1$ - taskService.save(task); + try { + Task task = new Task(); + for(fetchCursor.moveToFirst(); !fetchCursor.isAfterLast(); fetchCursor.moveToNext()) { + task.clear(); + task.readFromCursor(fetchCursor); + if(task.isCompleted()) + continue; + if(TextUtils.isEmpty(task.getValue(Task.DETAILS))) { + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DETAILS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId()); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, false); + activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + + taskDetailLoader.put(task.getId(), new StringBuilder()); + + task.setValue(Task.DETAILS, DETAIL_SEPARATOR); + taskService.save(task); + } } + } catch (Exception e) { + // suppress silently } } } @@ -787,7 +789,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable { abstract public class AddOnManager { private final Map> cache = - new SoftHashMap>(); + new HashMap>(); // --- interface diff --git a/astrid/src/com/todoroo/astrid/dao/TaskDao.java b/astrid/src/com/todoroo/astrid/dao/TaskDao.java index f55ba4c6a..490525359 100644 --- a/astrid/src/com/todoroo/astrid/dao/TaskDao.java +++ b/astrid/src/com/todoroo/astrid/dao/TaskDao.java @@ -159,7 +159,7 @@ public class TaskDao extends GenericDao { // clear task detail cache if(values != null && !values.containsKey(Task.DETAILS.name)) - values.put(Task.DETAILS.name, (String)null); + values.put(Task.DETAILS.name, ""); //$NON-NLS-1$ if (task.getId() == Task.NO_ID) { saveSuccessful = createNew(task); diff --git a/astrid/src/com/todoroo/astrid/service/TaskService.java b/astrid/src/com/todoroo/astrid/service/TaskService.java index af1481e7e..91e412f55 100644 --- a/astrid/src/com/todoroo/astrid/service/TaskService.java +++ b/astrid/src/com/todoroo/astrid/service/TaskService.java @@ -1,5 +1,7 @@ package com.todoroo.astrid.service; +import android.content.ContentValues; + import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.Autowired; @@ -228,9 +230,9 @@ public class TaskService { * @return # of affected rows */ public int clearDetails() { - Task task = new Task(); - task.setValue(Task.DETAILS, null); - return taskDao.updateMultiple(task, Criterion.all); + ContentValues values = new ContentValues(); + values.put(Task.DETAILS.name, (String) null); + return taskDao.updateMultiple(values, Criterion.all); } /** From c0e9ae6ff3610050b165410cf7e987d5d9965a8d Mon Sep 17 00:00:00 2001 From: Tim Su Date: Tue, 24 Aug 2010 12:48:39 -0700 Subject: [PATCH 16/17] Increased gesture detection threshold --- .../com/todoroo/andlib/widget/Api4GestureDetector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astrid/common-src/com/todoroo/andlib/widget/Api4GestureDetector.java b/astrid/common-src/com/todoroo/andlib/widget/Api4GestureDetector.java index 957f81772..11a110081 100644 --- a/astrid/common-src/com/todoroo/andlib/widget/Api4GestureDetector.java +++ b/astrid/common-src/com/todoroo/andlib/widget/Api4GestureDetector.java @@ -34,7 +34,7 @@ public class Api4GestureDetector implements OnGesturePerformedListener { if (predictions.size() > 0) { Prediction prediction = predictions.get(0); // We want at least some confidence in the result - if (prediction.score > 1.0) { + if (prediction.score > 2.0) { listener.gesturePerformed(prediction.name); } } From 888183c1bbf517ede35d67bef3918411e5a4399d Mon Sep 17 00:00:00 2001 From: Tim Su Date: Tue, 24 Aug 2010 12:49:15 -0700 Subject: [PATCH 17/17] Now we automatically feed our task to the decorations so no extra sql queries in the background. Also automatic updates when action button is pressed --- .../astrid/timers/TimerDecorationExposer.java | 23 +++++-- .../todoroo/astrid/timers/TimerPlugin.java | 2 +- astrid/res/layout/task_adapter_row.xml | 14 +++- astrid/res/layout/timer_decoration.xml | 8 +++ .../todoroo/astrid/adapter/TaskAdapter.java | 69 ++++++++++++++----- astrid/src/com/todoroo/astrid/model/Task.java | 2 +- 6 files changed, 94 insertions(+), 24 deletions(-) diff --git a/astrid/plugin-src/com/todoroo/astrid/timers/TimerDecorationExposer.java b/astrid/plugin-src/com/todoroo/astrid/timers/TimerDecorationExposer.java index 499f78e64..45b5159ac 100644 --- a/astrid/plugin-src/com/todoroo/astrid/timers/TimerDecorationExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/timers/TimerDecorationExposer.java @@ -11,6 +11,7 @@ import android.content.Intent; import android.graphics.Color; import android.os.SystemClock; import android.text.format.DateUtils; +import android.view.View; import android.widget.RemoteViews; import com.timsu.astrid.R; @@ -33,8 +34,12 @@ public class TimerDecorationExposer extends BroadcastReceiver { private static HashMap decorations = new HashMap(); - public static void removeFromCache(long taskId) { + private static HashMap tasksNeedingUpdate = + new HashMap(); + + public static void updateTimer(long taskId) { decorations.remove(taskId); + tasksNeedingUpdate.put(taskId, true); } @Override @@ -45,11 +50,15 @@ public class TimerDecorationExposer extends BroadcastReceiver { Task task; try { - task = PluginServices.getTaskService().fetchById(taskId, Task.ELAPSED_SECONDS, Task.TIMER_START); + if(tasksNeedingUpdate.containsKey(taskId)) + task = PluginServices.getTaskService().fetchById(taskId, Task.ID, Task.ELAPSED_SECONDS, Task.TIMER_START); + else + task = intent.getParcelableExtra("model"); //$NON-NLS-1$ } catch (IllegalStateException e) { return; } - if(task == null || (task.getValue(Task.ELAPSED_SECONDS) == 0 && + + if(task == null || task.getId() != taskId || (task.getValue(Task.ELAPSED_SECONDS) == 0 && task.getValue(Task.TIMER_START) == 0)) return; @@ -70,12 +79,16 @@ public class TimerDecorationExposer extends BroadcastReceiver { elapsed += DateUtilities.now() - task.getValue(Task.TIMER_START); decoration.decoration.setChronometer(R.id.timer, SystemClock.elapsedRealtime() - elapsed, null, true); + decoration.decoration.setViewVisibility(R.id.timer, View.VISIBLE); + decoration.decoration.setViewVisibility(R.id.label, View.GONE); } else { // if timer is not started, make the chronometer just a text label, // since we don't want the time to be displayed relative to elapsed String format = buildFormat(elapsed); - decoration.decoration.setChronometer(R.id.timer, SystemClock.elapsedRealtime() - - elapsed, format, false); + decoration.color = 0; + decoration.decoration.setTextViewText(R.id.label, format); + decoration.decoration.setViewVisibility(R.id.timer, View.GONE); + decoration.decoration.setViewVisibility(R.id.label, View.VISIBLE); } diff --git a/astrid/plugin-src/com/todoroo/astrid/timers/TimerPlugin.java b/astrid/plugin-src/com/todoroo/astrid/timers/TimerPlugin.java index 9a437ee2d..ab1439959 100644 --- a/astrid/plugin-src/com/todoroo/astrid/timers/TimerPlugin.java +++ b/astrid/plugin-src/com/todoroo/astrid/timers/TimerPlugin.java @@ -53,7 +53,7 @@ public class TimerPlugin extends BroadcastReceiver { } } PluginServices.getTaskService().save(task); - TimerDecorationExposer.removeFromCache(task.getId()); + TimerDecorationExposer.updateTimer(task.getId()); // transmit new intents Intent intent = new Intent(AstridApiConstants.BROADCAST_REQUEST_ACTIONS); diff --git a/astrid/res/layout/task_adapter_row.xml b/astrid/res/layout/task_adapter_row.xml index 751c74b8a..5dcf7f89d 100644 --- a/astrid/res/layout/task_adapter_row.xml +++ b/astrid/res/layout/task_adapter_row.xml @@ -72,6 +72,18 @@ android:visibility="gone" android:paddingTop="4dip" android:paddingLeft="4dip" - android:orientation="horizontal" /> + android:orientation="horizontal"> + +