Merge remote branch 'origin/master'

Conflicts:
	astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java
pull/14/head
Tim Su 16 years ago
commit 939bb9d5af

@ -54,7 +54,7 @@
<supports-screens /> <supports-screens />
<application android:icon="@drawable/icon" <application android:icon="@drawable/icon"
android:label="@string/app_name"> android:label="@string/app_name" android:debuggable="true">
<!-- ====================================================== Activities = --> <!-- ====================================================== Activities = -->
@ -189,6 +189,12 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name="com.todoroo.astrid.alarms.AlarmDetailExposer">
<intent-filter>
<action android:name="com.todoroo.astrid.REQUEST_DETAILS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- tags --> <!-- tags -->
<receiver android:name="com.todoroo.astrid.tags.TagsPlugin"> <receiver android:name="com.todoroo.astrid.tags.TagsPlugin">
@ -203,6 +209,12 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name="com.todoroo.astrid.tags.TagDetailExposer">
<intent-filter>
<action android:name="com.todoroo.astrid.REQUEST_DETAILS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- repeats --> <!-- repeats -->
<receiver android:name="com.todoroo.astrid.repeats.RepeatsPlugin"> <receiver android:name="com.todoroo.astrid.repeats.RepeatsPlugin">
@ -217,6 +229,12 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name="com.todoroo.astrid.repeats.RepeatDetailExposer">
<intent-filter>
<action android:name="com.todoroo.astrid.REQUEST_DETAILS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- calendar --> <!-- calendar -->
<receiver android:name="com.todoroo.astrid.gcal.GCalTaskCompleteListener"> <receiver android:name="com.todoroo.astrid.gcal.GCalTaskCompleteListener">
@ -255,6 +273,12 @@
</receiver> </receiver>
<!-- notes --> <!-- notes -->
<receiver android:name="com.todoroo.astrid.notes.NoteDetailExposer">
<intent-filter>
<action android:name="com.todoroo.astrid.REQUEST_DETAILS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- locale --> <!-- locale -->
<activity android:name="com.todoroo.astrid.locale.LocaleEditAlerts" <activity android:name="com.todoroo.astrid.locale.LocaleEditAlerts"
@ -346,6 +370,12 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name="com.todoroo.astrid.producteev.ProducteevDetailExposer">
<intent-filter>
<action android:name="com.todoroo.astrid.REQUEST_DETAILS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- rmilk --> <!-- rmilk -->
<receiver android:name="com.todoroo.astrid.rmilk.MilkFilterExposer"> <receiver android:name="com.todoroo.astrid.rmilk.MilkFilterExposer">
@ -374,6 +404,12 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name="com.todoroo.astrid.rmilk.MilkDetailExposer">
<intent-filter>
<action android:name="com.todoroo.astrid.REQUEST_DETAILS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
</application> </application>

@ -12,7 +12,7 @@
<intAttribute key="com.android.ide.eclipse.adt.delay" value="0"/> <intAttribute key="com.android.ide.eclipse.adt.delay" value="0"/>
<booleanAttribute key="com.android.ide.eclipse.adt.nobootanim" value="true"/> <booleanAttribute key="com.android.ide.eclipse.adt.nobootanim" value="true"/>
<intAttribute key="com.android.ide.eclipse.adt.speed" value="0"/> <intAttribute key="com.android.ide.eclipse.adt.speed" value="0"/>
<booleanAttribute key="com.android.ide.eclipse.adt.target" value="true"/> <booleanAttribute key="com.android.ide.eclipse.adt.target" value="false"/>
<booleanAttribute key="com.android.ide.eclipse.adt.wipedata" value="false"/> <booleanAttribute key="com.android.ide.eclipse.adt.wipedata" value="false"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS"> <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/astrid"/> <listEntry value="/astrid"/>

@ -196,6 +196,20 @@ public class GenericDao<TYPE extends AbstractModel> {
AbstractModel.ID_PROPERTY.eq(item.getId()).toString(), null) > 0; 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(ContentValues values, Criterion criterion) {
if(values.size() == 0) // nothing changed
return 0;
return database.update(table.name, values, criterion.toString(), null);
}
// --- helper methods // --- helper methods

@ -34,7 +34,7 @@ public class Api4GestureDetector implements OnGesturePerformedListener {
if (predictions.size() > 0) { if (predictions.size() > 0) {
Prediction prediction = predictions.get(0); Prediction prediction = predictions.get(0);
// We want at least some confidence in the result // We want at least some confidence in the result
if (prediction.score > 1.0) { if (prediction.score > 2.0) {
listener.gesturePerformed(prediction.name); listener.gesturePerformed(prediction.name);
} }
} }

@ -12,7 +12,6 @@ import com.timsu.astrid.R;
import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer;
import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.Metadata;
/** /**
@ -21,7 +20,7 @@ import com.todoroo.astrid.model.Metadata;
* @author Tim Su <tim@todoroo.com> * @author Tim Su <tim@todoroo.com>
* *
*/ */
public class AlarmDetailExposer extends BroadcastReceiver implements DetailExposer { public class AlarmDetailExposer extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@ -44,7 +43,6 @@ public class AlarmDetailExposer extends BroadcastReceiver implements DetailExpos
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
} }
@Override
public String getTaskDetails(Context context, long id, boolean extended) { public String getTaskDetails(Context context, long id, boolean extended) {
if(extended) if(extended)
return null; return null;
@ -71,9 +69,4 @@ public class AlarmDetailExposer extends BroadcastReceiver implements DetailExpos
} }
} }
@Override
public String getPluginIdentifier() {
return AlarmService.IDENTIFIER;
}
} }

@ -295,6 +295,12 @@ public abstract class SyncProvider<TYPE extends TaskContainer> {
length = data.remoteUpdated.size(); length = data.remoteUpdated.size();
for(int i = 0; i < length; i++) { for(int i = 0; i < length; i++) {
TYPE remote = data.remoteUpdated.get(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 { try {
write(remote); write(remote);
} catch (Exception e) { } catch (Exception e) {

@ -10,7 +10,6 @@ import android.content.Intent;
import com.timsu.astrid.R; import com.timsu.astrid.R;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer;
import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.model.Task; import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.utility.Preferences; import com.todoroo.astrid.utility.Preferences;
@ -21,7 +20,7 @@ import com.todoroo.astrid.utility.Preferences;
* @author Tim Su <tim@todoroo.com> * @author Tim Su <tim@todoroo.com>
* *
*/ */
public class NoteDetailExposer extends BroadcastReceiver implements DetailExposer { public class NoteDetailExposer extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@ -44,7 +43,6 @@ public class NoteDetailExposer extends BroadcastReceiver implements DetailExpose
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
} }
@Override
public String getTaskDetails(Context context, long id, boolean extended) { public String getTaskDetails(Context context, long id, boolean extended) {
if(Preferences.getBoolean(R.string.p_showNotes, false)) { if(Preferences.getBoolean(R.string.p_showNotes, false)) {
@ -65,9 +63,4 @@ public class NoteDetailExposer extends BroadcastReceiver implements DetailExpose
return "<img src='silk_note'/> " + notes; //$NON-NLS-1$ return "<img src='silk_note'/> " + notes; //$NON-NLS-1$
} }
@Override
public String getPluginIdentifier() {
return NotesPlugin.IDENTIFIER;
}
} }

@ -13,11 +13,11 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import android.widget.AdapterView.OnItemSelectedListener;
import com.timsu.astrid.R; import com.timsu.astrid.R;
import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.Autowired;
@ -94,10 +94,10 @@ public class ProducteevControlSet implements TaskEditControlSet {
dialog.cancel(); dialog.cancel();
} else { } else {
// create the real dashboard, select it in the spinner and refresh responsiblespinner // create the real dashboard, select it in the spinner and refresh responsiblespinner
ProgressDialog progressDialog = null; ProgressDialog progressDialog = dialogUtilites.progressDialog(context,
try {
progressDialog = dialogUtilites.progressDialog(context,
context.getString(R.string.DLG_wait)); context.getString(R.string.DLG_wait));
try {
progressDialog.show();
JSONObject newDashJSON = ProducteevSyncProvider.getInvoker().dashboardsCreate(newDashboardName).getJSONObject("dashboard"); JSONObject newDashJSON = ProducteevSyncProvider.getInvoker().dashboardsCreate(newDashboardName).getJSONObject("dashboard");
StoreObject local = ProducteevDataService.getInstance().updateDashboards(newDashJSON, true); StoreObject local = ProducteevDataService.getInstance().updateDashboards(newDashJSON, true);
if (local != null) { if (local != null) {

@ -11,7 +11,6 @@ import com.timsu.astrid.R;
import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.astrid.adapter.TaskAdapter; import com.todoroo.astrid.adapter.TaskAdapter;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer;
import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.StoreObject; import com.todoroo.astrid.model.StoreObject;
import com.todoroo.astrid.producteev.sync.ProducteevDashboard; import com.todoroo.astrid.producteev.sync.ProducteevDashboard;
@ -27,7 +26,7 @@ import com.todoroo.astrid.utility.Preferences;
* @author Tim Su <tim@todoroo.com> * @author Tim Su <tim@todoroo.com>
* *
*/ */
public class ProducteevDetailExposer extends BroadcastReceiver implements DetailExposer{ public class ProducteevDetailExposer extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@ -52,7 +51,6 @@ public class ProducteevDetailExposer extends BroadcastReceiver implements Detail
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
} }
@Override
public String getTaskDetails(Context context, long id, boolean extended) { public String getTaskDetails(Context context, long id, boolean extended) {
Metadata metadata = ProducteevDataService.getInstance().getTaskMetadata(id); Metadata metadata = ProducteevDataService.getInstance().getTaskMetadata(id);
if(metadata == null) if(metadata == null)
@ -133,9 +131,4 @@ public class ProducteevDetailExposer extends BroadcastReceiver implements Detail
return null; return null;
} }
@Override
public String getPluginIdentifier() {
return ProducteevUtilities.IDENTIFIER;
}
} }

@ -3,8 +3,8 @@
*/ */
package com.todoroo.astrid.producteev; package com.todoroo.astrid.producteev;
import java.util.Map.Entry;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.Map.Entry;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ContentValues; import android.content.ContentValues;
@ -39,7 +39,7 @@ public class ProducteevFilterExposer extends BroadcastReceiver {
/** /**
* @param context * @param context
*/ */
private Filter filterFromList(Context context, ProducteevDashboard dashboard) { public static Filter filterFromList(Context context, ProducteevDashboard dashboard) {
String dashboardTitle = dashboard.getName(); String dashboardTitle = dashboard.getName();
String title = dashboard.getName(); String title = dashboard.getName();
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();

@ -41,6 +41,10 @@ public class ProducteevUtilities extends SyncProviderUtilities {
public static final String PREF_SERVER_LAST_SYNC = IDENTIFIER + "_last_server"; //$NON-NLS-1$ public static final String PREF_SERVER_LAST_SYNC = IDENTIFIER + "_last_server"; //$NON-NLS-1$
public static final String PREF_SERVER_LAST_NOTIFICATION = IDENTIFIER + "_last_notification"; //$NON-NLS-1$
public static final String PREF_SERVER_LAST_ACTIVITY = IDENTIFIER + "_last_activity"; //$NON-NLS-1$
/** Producteev user's default dashboard. This is different from the /** Producteev user's default dashboard. This is different from the
* preference key, which indicates where user wants to put new tasks */ * preference key, which indicates where user wants to put new tasks */
public static final String PREF_DEFAULT_DASHBOARD = IDENTIFIER + "_defaultdash"; //$NON-NLS-1$ public static final String PREF_DEFAULT_DASHBOARD = IDENTIFIER + "_defaultdash"; //$NON-NLS-1$

@ -111,6 +111,31 @@ public class ProducteevInvoker {
"since", since), "dashboards"); "since", since), "dashboards");
} }
/**
* 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
*
* @param idDashboard
* @param dashboard array-information about the dashboard, if this ...
*/
public JSONArray dashboardsAccess(long idDashboard, String dashboard) throws ApiServiceException, IOException {
return getResponse(callAuthenticated("dashboards/access.json",
"token", token,
"id_dashboard", idDashboard,
"dashboard", dashboard),"dashboard");
}
// --- tasks // --- tasks
/** /**
@ -217,14 +242,16 @@ public class ProducteevInvoker {
* *
* @param idTask * @param idTask
* @param deadline * @param deadline
* @param allDay (optional), 1: all day task (time specified in deadline will be ignored), 0: deadline with time
* *
* @return array tasks/view * @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", return callAuthenticated("tasks/set_deadline.json",
"token", token, "token", token,
"id_task", idTask, "id_task", idTask,
"deadline", deadline); "deadline", deadline,
"all_day", allDay);
} }
/** /**
@ -374,44 +401,49 @@ public class ProducteevInvoker {
"title", title); "title", title);
} }
// --- users // --- notifications/activities
/** /**
* get a user * get every activities
*
* @param idColleague
* *
* @return array information about the user * @param dashboardId (optional) if not null, this function only returns notifications for this specific dashboard
* @param lastId (optional) this function returns only activities later than this id
*/ */
public JSONObject usersView(Long idColleague) throws ApiServiceException, IOException { public JSONArray activitiesShowActivities(Long dashboardId, Long lastId) throws ApiResponseParseException, ApiServiceException, IOException {
return callAuthenticated("users/view.json", return getResponse(callAuthenticated("activities/show_activities.json",
"token", token, "token", token,
"id_colleague", idColleague); "id_dashboard", dashboardId,
"last_id", lastId), "activities");
} }
/** /**
* create a dasbhoard * get every notification for the current user
* * @param dashboardId
* @param name * @param lastId
* @return the new created dashboard as JSONObject * @return
* @throws ApiResponseParseException
* @throws ApiServiceException
* @throws IOException
*/ */
public JSONObject dashboardsCreate(String name) throws ApiServiceException, IOException { public JSONArray activitiesShowNotifications(Long dashboardId, Long lastId) throws ApiResponseParseException, ApiServiceException, IOException {
return callAuthenticated("dashboards/create.json", return getResponse(callAuthenticated("activities/show_notifications.json",
"token", token, "token", token,
"title", name); "id_dashboard", dashboardId,
"last_id", lastId), "activities");
} }
// --- users
/** /**
* return the list of users who can access a specific dashboard * get a user
* *
* @param idDashboard * @param idColleague
* @param dashboard array-information about the dashboard, if this ... *
* @return array information about the user
*/ */
public JSONArray dashboardsAccess(long idDashboard, String dashboard) throws ApiServiceException, IOException { public JSONObject usersView(Long idColleague) throws ApiServiceException, IOException {
return getResponse(callAuthenticated("dashboards/access.json", return callAuthenticated("users/view.json",
"token", token, "token", token,
"id_dashboard", idDashboard, "id_colleague", idColleague);
"dashboard", dashboard),"dashboard");
} }
// --- invocation // --- invocation

@ -20,15 +20,17 @@ import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.sql.Criterion; import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Join; import com.todoroo.andlib.sql.Join;
import com.todoroo.andlib.sql.Query; 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;
import com.todoroo.astrid.dao.StoreObjectDao.StoreObjectCriteria;
import com.todoroo.astrid.dao.TaskDao; 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.dao.TaskDao.TaskCriteria;
import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.StoreObject; import com.todoroo.astrid.model.StoreObject;
import com.todoroo.astrid.model.Task; import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.producteev.ProducteevUtilities; import com.todoroo.astrid.producteev.ProducteevUtilities;
import com.todoroo.astrid.producteev.api.ApiUtilities;
import com.todoroo.astrid.rmilk.data.MilkNote; import com.todoroo.astrid.rmilk.data.MilkNote;
import com.todoroo.astrid.service.MetadataService; import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.tags.TagService; 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(ProducteevTask.METADATA_KEY));
metadataService.deleteWhere(Metadata.KEY.eq(ProducteevNote.METADATA_KEY)); metadataService.deleteWhere(Metadata.KEY.eq(ProducteevNote.METADATA_KEY));
storeObjectDao.deleteWhere(StoreObject.TYPE.eq(ProducteevDashboard.TYPE)); storeObjectDao.deleteWhere(StoreObject.TYPE.eq(ProducteevDashboard.TYPE));
PluginServices.getTaskService().clearDetails();
} }
/** /**
@ -272,15 +275,15 @@ public final class ProducteevDataService {
local = new StoreObject(); local = new StoreObject();
local.setValue(StoreObject.TYPE, ProducteevDashboard.TYPE); local.setValue(StoreObject.TYPE, ProducteevDashboard.TYPE);
local.setValue(ProducteevDashboard.REMOTE_ID, id); 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(); StringBuilder users = new StringBuilder();
JSONArray accessList = remote.getJSONArray("accesslist"); JSONArray accessList = remote.getJSONArray("accesslist");
for(int j = 0; j < accessList.length(); j++) { for(int j = 0; j < accessList.length(); j++) {
JSONObject user = accessList.getJSONObject(j).getJSONObject("user"); JSONObject user = accessList.getJSONObject(j).getJSONObject("user");
users.append(user.getLong("id_user")).append(','); users.append(user.getLong("id_user")).append(',');
String name = user.optString("firstname", "") + ' ' + String name = ApiUtilities.decode(user.optString("firstname", "") + ' ' +
user.optString("lastname", ""); user.optString("lastname", ""));
users.append(name.trim()).append(';'); users.append(name.trim()).append(';');
} }
local.setValue(ProducteevDashboard.USERS, users.toString()); local.setValue(ProducteevDashboard.USERS, users.toString());

@ -28,16 +28,21 @@ import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.service.NotificationManager;
import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.activity.ShortcutActivity;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.TaskContainer;
import com.todoroo.astrid.common.SyncProvider; import com.todoroo.astrid.common.SyncProvider;
import com.todoroo.astrid.common.TaskContainer; import com.todoroo.astrid.common.TaskContainer;
import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.StoreObject; import com.todoroo.astrid.model.StoreObject;
import com.todoroo.astrid.model.Task; import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.producteev.ProducteevBackgroundService; import com.todoroo.astrid.producteev.ProducteevBackgroundService;
import com.todoroo.astrid.producteev.ProducteevFilterExposer;
import com.todoroo.astrid.producteev.ProducteevLoginActivity; import com.todoroo.astrid.producteev.ProducteevLoginActivity;
import com.todoroo.astrid.producteev.ProducteevPreferences; import com.todoroo.astrid.producteev.ProducteevPreferences;
import com.todoroo.astrid.producteev.ProducteevUtilities; import com.todoroo.astrid.producteev.ProducteevUtilities;
@ -48,6 +53,7 @@ import com.todoroo.astrid.producteev.api.ProducteevInvoker;
import com.todoroo.astrid.rmilk.data.MilkNote; import com.todoroo.astrid.rmilk.data.MilkNote;
import com.todoroo.astrid.service.AstridDependencyInjector; import com.todoroo.astrid.service.AstridDependencyInjector;
import com.todoroo.astrid.tags.TagService; import com.todoroo.astrid.tags.TagService;
import com.todoroo.astrid.utility.Constants;
import com.todoroo.astrid.utility.Preferences; import com.todoroo.astrid.utility.Preferences;
@SuppressWarnings("nls") @SuppressWarnings("nls")
@ -213,6 +219,8 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
long userId = user.getLong("id_user"); long userId = user.getLong("id_user");
String lastServerSync = Preferences.getStringValue(ProducteevUtilities.PREF_SERVER_LAST_SYNC); String lastServerSync = Preferences.getStringValue(ProducteevUtilities.PREF_SERVER_LAST_SYNC);
String lastNotificationId = Preferences.getStringValue(ProducteevUtilities.PREF_SERVER_LAST_NOTIFICATION);
String lastActivityId = Preferences.getStringValue(ProducteevUtilities.PREF_SERVER_LAST_ACTIVITY);
// read dashboards // read dashboards
JSONArray dashboards = invoker.dashboardsShowList(null); JSONArray dashboards = invoker.dashboardsShowList(null);
@ -260,6 +268,56 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_REFRESH); Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_REFRESH);
ContextManager.getContext().sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); ContextManager.getContext().sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
// notification/activities stuff
JSONArray notifications = invoker.activitiesShowNotifications(null, (lastNotificationId == null ? null : new Long(lastNotificationId)));
String[] notificationsList = parseActivities(notifications);
// update lastIds
if (notifications.length() > 0) {
lastNotificationId = ""+notifications.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.length; i++) {
long id_dashboard = notifications.getJSONObject(i).getJSONObject("activity").getLong("id_dashboard");
String dashboardName = null;
StoreObject[] dashboardsData = ProducteevDataService.getInstance().getDashboards();
ProducteevDashboard dashboard = null;
if (dashboardsData != null) {
for (int j=0; i<dashboardsData.length;i++) {
long id = dashboardsData[j].getValue(ProducteevDashboard.REMOTE_ID);
if (id == id_dashboard) {
dashboardName = dashboardsData[j].getValue(ProducteevDashboard.NAME);
dashboard = new ProducteevDashboard(id, dashboardName, null);
break;
}
}
}
// it seems dashboard is null if we get a notification about an unknown dashboard, just filter it.
if (dashboard != null) {
// initialize notification
int icon = R.drawable.ic_producteev_notification;
long when = System.currentTimeMillis();
Notification notification = new Notification(icon, null, when);
CharSequence contentTitle = context.getString(R.string.producteev_notification_title)+": "+dashboard.getName();
Filter filter = ProducteevFilterExposer.filterFromList(context, dashboard);
Intent notificationIntent = ShortcutActivity.createIntent(filter);
// filter the tags from the message
String message = notificationsList[i].replaceAll("<[^>]+>", "");
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
notification.setLatestEventInfo(context, contentTitle, message, contentIntent);
nm.notify(Constants.NOTIFICATION_PRODUCTEEV_NOTIFICATIONS-i, notification);
}
}
// store lastIds in Preferences
Preferences.setString(ProducteevUtilities.PREF_SERVER_LAST_NOTIFICATION, lastNotificationId);
Preferences.setString(ProducteevUtilities.PREF_SERVER_LAST_ACTIVITY, lastActivityId);
FlurryAgent.onEvent("pdv-sync-finished"); //$NON-NLS-1$ FlurryAgent.onEvent("pdv-sync-finished"); //$NON-NLS-1$
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
// occurs when application was closed // occurs when application was closed
@ -268,6 +326,23 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
} }
} }
/**
* @param activities
* @return
* @throws JSONException
*/
private String[] parseActivities(JSONArray activities) throws JSONException {
int count = (activities == null ? 0 : activities.length());
String[] activitiesList = new String[count];
if(activities == null)
return activitiesList;
for(int i = 0; i < activities.length(); i++) {
String message = activities.getJSONObject(i).getJSONObject("activity").getString("message");
activitiesList[i] = ApiUtilities.decode(message);
}
return activitiesList;
}
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// ------------------------------------------------------------ sync data // ------------------------------------------------------------ sync data
@ -360,6 +435,9 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
task.setValue(Task.DELETION_DATE, remoteTask.getInt("deleted") == 1 ? DateUtilities.now() : 0); task.setValue(Task.DELETION_DATE, remoteTask.getInt("deleted") == 1 ? DateUtilities.now() : 0);
long dueDate = ApiUtilities.producteevToUnixTime(remoteTask.getString("deadline"), 0); long dueDate = ApiUtilities.producteevToUnixTime(remoteTask.getString("deadline"), 0);
if(remoteTask.optInt("all_day", 0) == 1)
task.setValue(Task.DUE_DATE, task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate));
else
task.setValue(Task.DUE_DATE, task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, dueDate)); task.setValue(Task.DUE_DATE, task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, dueDate));
task.setValue(Task.IMPORTANCE, 5 - remoteTask.getInt("star")); task.setValue(Task.IMPORTANCE, 5 - remoteTask.getInt("star"));
@ -454,8 +532,8 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
invoker.tasksSetTitle(idTask, local.task.getValue(Task.TITLE)); invoker.tasksSetTitle(idTask, local.task.getValue(Task.TITLE));
if(shouldTransmit(local, Task.IMPORTANCE, remote)) if(shouldTransmit(local, Task.IMPORTANCE, remote))
invoker.tasksSetStar(idTask, createStars(local.task)); invoker.tasksSetStar(idTask, createStars(local.task));
if(shouldTransmit(local, Task.DUE_DATE, remote)) if(shouldTransmit(local, Task.DUE_DATE, remote) && local.task.hasDueDate()) // temporary can't unset deadline
invoker.tasksSetDeadline(idTask, createDeadline(local.task)); invoker.tasksSetDeadline(idTask, createDeadline(local.task), local.task.hasDueTime() ? 0 : 1);
if(shouldTransmit(local, Task.COMPLETION_DATE, remote)) if(shouldTransmit(local, Task.COMPLETION_DATE, remote))
invoker.tasksSetStatus(idTask, local.task.isCompleted() ? 2 : 1); invoker.tasksSetStatus(idTask, local.task.isCompleted() ? 2 : 1);
@ -597,9 +675,8 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
*/ */
private String createDeadline(Task task) { private String createDeadline(Task task) {
if(!task.hasDueDate()) if(!task.hasDueDate())
return null; return "";
String time = ApiUtilities.unixTimeToProducteev(task.getValue(Task.DUE_DATE)); return ApiUtilities.unixTimeToProducteev(task.getValue(Task.DUE_DATE));
return time.substring(0, time.lastIndexOf(' '));
} }
/** /**

@ -17,7 +17,6 @@ import com.google.ical.values.RRule;
import com.google.ical.values.WeekdayNum; import com.google.ical.values.WeekdayNum;
import com.timsu.astrid.R; import com.timsu.astrid.R;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer;
import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.model.Task; import com.todoroo.astrid.model.Task;
@ -27,7 +26,7 @@ import com.todoroo.astrid.model.Task;
* @author Tim Su <tim@todoroo.com> * @author Tim Su <tim@todoroo.com>
* *
*/ */
public class RepeatDetailExposer extends BroadcastReceiver implements DetailExposer { public class RepeatDetailExposer extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@ -119,7 +118,6 @@ public class RepeatDetailExposer extends BroadcastReceiver implements DetailExpo
return null; return null;
} }
@Override
public String getPluginIdentifier() { public String getPluginIdentifier() {
return RepeatsPlugin.IDENTIFIER; return RepeatsPlugin.IDENTIFIER;
} }

@ -11,7 +11,6 @@ import com.timsu.astrid.R;
import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.astrid.adapter.TaskAdapter; import com.todoroo.astrid.adapter.TaskAdapter;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer;
import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.rmilk.data.MilkDataService; import com.todoroo.astrid.rmilk.data.MilkDataService;
import com.todoroo.astrid.rmilk.data.MilkNote; import com.todoroo.astrid.rmilk.data.MilkNote;
@ -26,7 +25,7 @@ import com.todoroo.astrid.rmilk.data.MilkTask;
* @author Tim Su <tim@todoroo.com> * @author Tim Su <tim@todoroo.com>
* *
*/ */
public class MilkDetailExposer extends BroadcastReceiver implements DetailExposer{ public class MilkDetailExposer extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@ -51,7 +50,6 @@ public class MilkDetailExposer extends BroadcastReceiver implements DetailExpose
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
} }
@Override
public String getTaskDetails(Context context, long id, boolean extended) { public String getTaskDetails(Context context, long id, boolean extended) {
Metadata metadata = MilkDataService.getInstance().getTaskMetadata(id); Metadata metadata = MilkDataService.getInstance().getTaskMetadata(id);
if(metadata == null) if(metadata == null)
@ -92,9 +90,4 @@ public class MilkDetailExposer extends BroadcastReceiver implements DetailExpose
return result.substring(0, result.length() - TaskAdapter.DETAIL_SEPARATOR.length()); return result.substring(0, result.length() - TaskAdapter.DETAIL_SEPARATOR.length());
} }
@Override
public String getPluginIdentifier() {
return MilkUtilities.IDENTIFIER;
}
} }

@ -8,7 +8,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer;
/** /**
* Exposes Task Detail for tags, i.e. "Tags: frogs, animals" * Exposes Task Detail for tags, i.e. "Tags: frogs, animals"
@ -16,7 +15,7 @@ import com.todoroo.astrid.api.DetailExposer;
* @author Tim Su <tim@todoroo.com> * @author Tim Su <tim@todoroo.com>
* *
*/ */
public class TagDetailExposer extends BroadcastReceiver implements DetailExposer { public class TagDetailExposer extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@ -39,7 +38,6 @@ public class TagDetailExposer extends BroadcastReceiver implements DetailExposer
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
} }
@Override
public String getTaskDetails(Context context, long id, boolean extended) { public String getTaskDetails(Context context, long id, boolean extended) {
if(extended) if(extended)
return null; return null;
@ -51,9 +49,4 @@ public class TagDetailExposer extends BroadcastReceiver implements DetailExposer
return "<img src='silk_tag_pink'/> " + tagList; //$NON-NLS-1$ return "<img src='silk_tag_pink'/> " + tagList; //$NON-NLS-1$
} }
@Override
public String getPluginIdentifier() {
return TagsPlugin.IDENTIFIER;
}
} }

@ -11,6 +11,7 @@ import android.content.Intent;
import android.graphics.Color; import android.graphics.Color;
import android.os.SystemClock; import android.os.SystemClock;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.view.View;
import android.widget.RemoteViews; import android.widget.RemoteViews;
import com.timsu.astrid.R; import com.timsu.astrid.R;
@ -33,8 +34,12 @@ public class TimerDecorationExposer extends BroadcastReceiver {
private static HashMap<Long, TaskDecoration> decorations = private static HashMap<Long, TaskDecoration> decorations =
new HashMap<Long, TaskDecoration>(); new HashMap<Long, TaskDecoration>();
public static void removeFromCache(long taskId) { private static HashMap<Long, Boolean> tasksNeedingUpdate =
new HashMap<Long, Boolean>();
public static void updateTimer(long taskId) {
decorations.remove(taskId); decorations.remove(taskId);
tasksNeedingUpdate.put(taskId, true);
} }
@Override @Override
@ -45,11 +50,15 @@ public class TimerDecorationExposer extends BroadcastReceiver {
Task task; Task task;
try { 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) { } catch (IllegalStateException e) {
return; 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)) task.getValue(Task.TIMER_START) == 0))
return; return;
@ -70,12 +79,16 @@ public class TimerDecorationExposer extends BroadcastReceiver {
elapsed += DateUtilities.now() - task.getValue(Task.TIMER_START); elapsed += DateUtilities.now() - task.getValue(Task.TIMER_START);
decoration.decoration.setChronometer(R.id.timer, SystemClock.elapsedRealtime() - decoration.decoration.setChronometer(R.id.timer, SystemClock.elapsedRealtime() -
elapsed, null, true); elapsed, null, true);
decoration.decoration.setViewVisibility(R.id.timer, View.VISIBLE);
decoration.decoration.setViewVisibility(R.id.label, View.GONE);
} else { } else {
// if timer is not started, make the chronometer just a text label, // 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 // since we don't want the time to be displayed relative to elapsed
String format = buildFormat(elapsed); String format = buildFormat(elapsed);
decoration.decoration.setChronometer(R.id.timer, SystemClock.elapsedRealtime() - decoration.color = 0;
elapsed, format, false); decoration.decoration.setTextViewText(R.id.label, format);
decoration.decoration.setViewVisibility(R.id.timer, View.GONE);
decoration.decoration.setViewVisibility(R.id.label, View.VISIBLE);
} }

@ -53,7 +53,7 @@ public class TimerPlugin extends BroadcastReceiver {
} }
} }
PluginServices.getTaskService().save(task); PluginServices.getTaskService().save(task);
TimerDecorationExposer.removeFromCache(task.getId()); TimerDecorationExposer.updateTimer(task.getId());
// transmit new intents // transmit new intents
Intent intent = new Intent(AstridApiConstants.BROADCAST_REQUEST_ACTIONS); Intent intent = new Intent(AstridApiConstants.BROADCAST_REQUEST_ACTIONS);

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 773 B

@ -8,13 +8,13 @@
android:paddingBottom="4dip" android:paddingBottom="4dip"
android:paddingLeft="4dip" android:paddingLeft="4dip"
android:paddingRight="4dip" android:paddingRight="4dip"
android:minHeight="40dip"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout android:id="@+id/task_row" <LinearLayout android:id="@+id/task_row"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="100" android:layout_weight="100"
android:minHeight="40dip"
android:orientation="horizontal"> android:orientation="horizontal">
<!-- importance --> <!-- importance -->
@ -62,6 +62,7 @@
<TextView android:id="@+id/extendedDetails" <TextView android:id="@+id/extendedDetails"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingLeft="4dip"
android:visibility="gone" /> android:visibility="gone" />
<LinearLayout android:id="@+id/actions" <LinearLayout android:id="@+id/actions"
@ -70,6 +71,19 @@
android:background="#4499bbcc" android:background="#4499bbcc"
android:visibility="gone" android:visibility="gone"
android:paddingTop="4dip" android:paddingTop="4dip"
android:orientation="horizontal" /> android:paddingLeft="4dip"
android:orientation="horizontal">
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout> </LinearLayout>

@ -15,6 +15,14 @@
android:src="@drawable/timers_decoration" /> android:src="@drawable/timers_decoration" />
<Chronometer android:id="@+id/timer" <Chronometer android:id="@+id/timer"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:gravity="top"
android:visibility="gone"
android:textSize="10sp" />
<TextView android:id="@+id/label"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="2" android:layout_weight="2"

@ -215,6 +215,9 @@ button: add task & go to the edit page.
<!-- Context Item: undelete task --> <!-- Context Item: undelete task -->
<string name="TAd_contextUndeleteTask">Undelete Task</string> <string name="TAd_contextUndeleteTask">Undelete Task</string>
<!-- Context Item: purge task -->
<string name="TAd_contextPurgeTask">Purge Task</string>
<!-- ============================================== SortSelectionDialog == --> <!-- ============================================== SortSelectionDialog == -->
<!-- Sort Selection: dialog title --> <!-- Sort Selection: dialog title -->

@ -92,8 +92,11 @@
<!-- ================================================ Synchronization == --> <!-- ================================================ Synchronization == -->
<!-- title for notification tray when synchronizing --> <!-- title for notification tray after synchronizing -->
<string name="producteev_notification_title">Astrid: Producteev</string> <string name="producteev_notification_title">Producteev</string>
<!-- text for notification tray when synchronizing -->
<string name="producteev_notification_text">%s tasks updated / click for more details</string>
<!-- Error msg when io exception --> <!-- Error msg when io exception -->
<string name="producteev_ioerror">Connection Error! Check your Internet connection.</string> <string name="producteev_ioerror">Connection Error! Check your Internet connection.</string>

@ -14,9 +14,10 @@ import android.content.pm.ResolveInfo;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.preference.Preference; import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceCategory; import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import android.preference.Preference.OnPreferenceClickListener;
import android.widget.Toast;
import com.timsu.astrid.R; import com.timsu.astrid.R;
import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.Autowired;
@ -130,6 +131,18 @@ public class EditPreferences extends TodorooPreferences {
getPreferenceScreen().addPreference(group); getPreferenceScreen().addPreference(group);
Preference preference = new Preference(this); Preference preference = new Preference(this);
preference.setTitle("Flush detail cache");
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference p) {
database.openForWriting();
Toast.makeText(EditPreferences.this, "" + taskService.clearDetails(),
Toast.LENGTH_LONG).show();
return false;
}
});
group.addPreference(preference);
preference = new Preference(this);
preference.setTitle("Make Lots of Tasks"); preference.setTitle("Make Lots of Tasks");
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() { preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference p) { public boolean onPreferenceClick(Preference p) {

@ -116,10 +116,11 @@ 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_EDIT_TASK_ID = Menu.FIRST + 6;
private static final int CONTEXT_MENU_DELETE_TASK_ID = Menu.FIRST + 7; 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_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 */ /** menu code indicating the end of the context menu */
private static final int CONTEXT_MENU_DEBUG = Menu.FIRST + 10; private static final int CONTEXT_MENU_DEBUG = Menu.FIRST + 11;
// --- constants // --- constants
@ -479,9 +480,9 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
} else if(AstridApiConstants.BROADCAST_SEND_DETAILS.equals(intent.getAction())) { } else if(AstridApiConstants.BROADCAST_SEND_DETAILS.equals(intent.getAction())) {
String detail = extras.getString(AstridApiConstants.EXTRAS_RESPONSE); String detail = extras.getString(AstridApiConstants.EXTRAS_RESPONSE);
if(extras.getBoolean(AstridApiConstants.EXTRAS_EXTENDED)) if(extras.getBoolean(AstridApiConstants.EXTRAS_EXTENDED))
taskAdapter.detailManager.addNew(taskId, addOn, detail);
else
taskAdapter.extendedDetailManager.addNew(taskId, addOn, detail); taskAdapter.extendedDetailManager.addNew(taskId, addOn, detail);
else
taskAdapter.addDetails(taskId, detail);
} else if(AstridApiConstants.BROADCAST_SEND_ACTIONS.equals(intent.getAction())) { } else if(AstridApiConstants.BROADCAST_SEND_ACTIONS.equals(intent.getAction())) {
TaskAction action = extras.getParcelable(AstridApiConstants.EXTRAS_RESPONSE); TaskAction action = extras.getParcelable(AstridApiConstants.EXTRAS_RESPONSE);
taskAdapter.taskActionManager.addNew(taskId, addOn, action); taskAdapter.taskActionManager.addNew(taskId, addOn, action);
@ -736,6 +737,9 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
if(task.isDeleted()) { if(task.isDeleted()) {
menu.add(id, CONTEXT_MENU_UNDELETE_TASK_ID, Menu.NONE, menu.add(id, CONTEXT_MENU_UNDELETE_TASK_ID, Menu.NONE,
R.string.TAd_contextUndeleteTask); R.string.TAd_contextUndeleteTask);
menu.add(id, CONTEXT_MENU_PURGE_TASK_ID, Menu.NONE,
R.string.TAd_contextPurgeTask);
} else { } else {
menu.add(id, CONTEXT_MENU_EDIT_TASK_ID, Menu.NONE, menu.add(id, CONTEXT_MENU_EDIT_TASK_ID, Menu.NONE,
R.string.TAd_contextEditTask); R.string.TAd_contextEditTask);
@ -845,6 +849,13 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
return true; return true;
} }
case CONTEXT_MENU_PURGE_TASK_ID: {
itemId = item.getGroupId();
taskService.purge(itemId);
loadTaskListContent(true);
return true;
}
// --- debug // --- debug
case CONTEXT_MENU_DEBUG: { case CONTEXT_MENU_DEBUG: {

@ -1,6 +1,7 @@
package com.todoroo.astrid.adapter; package com.todoroo.astrid.adapter;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
@ -12,9 +13,11 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.text.Html; import android.text.Html;
import android.text.TextUtils;
import android.text.Html.ImageGetter; import android.text.Html.ImageGetter;
import android.text.util.Linkify; import android.text.util.Linkify;
import android.view.ContextMenu; import android.view.ContextMenu;
@ -40,21 +43,14 @@ import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.SoftHashMap;
import com.todoroo.astrid.activity.TaskEditActivity; import com.todoroo.astrid.activity.TaskEditActivity;
import com.todoroo.astrid.activity.TaskListActivity; import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.alarms.AlarmDetailExposer;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer;
import com.todoroo.astrid.api.TaskAction; import com.todoroo.astrid.api.TaskAction;
import com.todoroo.astrid.api.TaskDecoration; import com.todoroo.astrid.api.TaskDecoration;
import com.todoroo.astrid.model.Task; import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.notes.NoteDetailExposer; import com.todoroo.astrid.service.AddOnService;
import com.todoroo.astrid.producteev.ProducteevDetailExposer;
import com.todoroo.astrid.repeats.RepeatDetailExposer;
import com.todoroo.astrid.rmilk.MilkDetailExposer;
import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.tags.TagDetailExposer;
import com.todoroo.astrid.utility.Constants; import com.todoroo.astrid.utility.Constants;
import com.todoroo.astrid.utility.Preferences; import com.todoroo.astrid.utility.Preferences;
@ -72,6 +68,8 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
public static final String DETAIL_SEPARATOR = " | "; //$NON-NLS-1$ public static final String DETAIL_SEPARATOR = " | "; //$NON-NLS-1$
public static final String BROADCAST_EXTRA_TASK = "model"; //$NON-NLS-1$
// --- other constants // --- other constants
/** Properties that need to be read from the action item */ /** Properties that need to be read from the action item */
@ -83,16 +81,9 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
Task.COMPLETION_DATE, Task.COMPLETION_DATE,
Task.HIDE_UNTIL, Task.HIDE_UNTIL,
Task.DELETION_DATE, Task.DELETION_DATE,
}; Task.DETAILS,
Task.ELAPSED_SECONDS,
/** Internal Task Detail exposers */ Task.TIMER_START,
public static final DetailExposer[] EXPOSERS = new DetailExposer[] {
new TagDetailExposer(),
new RepeatDetailExposer(),
new NoteDetailExposer(),
new MilkDetailExposer(),
new ProducteevDetailExposer(),
new AlarmDetailExposer(),
}; };
private static int[] IMPORTANCE_COLORS = null; private static int[] IMPORTANCE_COLORS = null;
@ -105,6 +96,9 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
@Autowired @Autowired
private TaskService taskService; private TaskService taskService;
@Autowired
private AddOnService addOnService;
protected final ListActivity activity; protected final ListActivity activity;
protected final HashMap<Long, Boolean> completedItems = new HashMap<Long, Boolean>(); protected final HashMap<Long, Boolean> completedItems = new HashMap<Long, Boolean>();
private OnCompletedTaskListener onCompletedTaskListener = null; private OnCompletedTaskListener onCompletedTaskListener = null;
@ -112,6 +106,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
private final int resource; private final int resource;
private final LayoutInflater inflater; private final LayoutInflater inflater;
private int fontSize; private int fontSize;
private DetailLoaderThread detailLoader;
private final AtomicReference<String> query; private final AtomicReference<String> query;
@ -120,8 +115,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
// --- task detail and decoration soft caches // --- task detail and decoration soft caches
public final DetailManager detailManager = new DetailManager(false); public final DetailManager extendedDetailManager = new DetailManager();
public final DetailManager extendedDetailManager = new DetailManager(true);
public final DecorationManager decorationManager = public final DecorationManager decorationManager =
new DecorationManager(); new DecorationManager();
public final TaskActionManager taskActionManager = new TaskActionManager(); public final TaskActionManager taskActionManager = new TaskActionManager();
@ -159,6 +153,9 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
if(IMPORTANCE_COLORS == null) if(IMPORTANCE_COLORS == null)
IMPORTANCE_COLORS = Task.getImportanceColors(activity.getResources()); IMPORTANCE_COLORS = Task.getImportanceColors(activity.getResources());
} }
detailLoader = new DetailLoaderThread();
detailLoader.start();
} }
/* ====================================================================== /* ======================================================================
@ -221,6 +218,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
ViewHolder viewHolder = ((ViewHolder)view.getTag()); ViewHolder viewHolder = ((ViewHolder)view.getTag());
Task task = viewHolder.task; Task task = viewHolder.task;
task.clear();
task.readFromCursor(cursor); task.readFromCursor(cursor);
setFieldContentsAndVisibility(view); setFieldContentsAndVisibility(view);
@ -271,6 +269,8 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
if(hiddenUntil > DateUtilities.now()) if(hiddenUntil > DateUtilities.now())
nameValue = r.getString(R.string.TAd_hiddenFormat, nameValue); nameValue = r.getString(R.string.TAd_hiddenFormat, nameValue);
nameView.setText(nameValue); nameView.setText(nameValue);
nameView.setMovementMethod(null);
Linkify.addLinks(nameView, Linkify.ALL); Linkify.addLinks(nameView, Linkify.ALL);
} }
@ -324,9 +324,21 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
importanceView.setBackgroundColor(0); importanceView.setBackgroundColor(0);
} }
String details;
if(taskDetailLoader.containsKey(task.getId()))
details = taskDetailLoader.get(task.getId()).toString();
else
details = task.getValue(Task.DETAILS);
if(TextUtils.isEmpty(details) || DETAIL_SEPARATOR.equals(details)) {
viewHolder.details.setVisibility(View.GONE);
} else {
viewHolder.details.setVisibility(View.VISIBLE);
viewHolder.details.setText(Html.fromHtml(details.trim().replace("\n", //$NON-NLS-1$
"<br>"), detailImageGetter, null)); //$NON-NLS-1$
}
// details and decorations, expanded // details and decorations, expanded
if(!isFling) { if(!isFling) {
detailManager.request(viewHolder);
decorationManager.request(viewHolder); decorationManager.request(viewHolder);
if(expanded == task.getId()) { if(expanded == task.getId()) {
extendedDetailManager.request(viewHolder); extendedDetailManager.request(viewHolder);
@ -337,8 +349,9 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
} }
} else { } else {
long taskId = viewHolder.task.getId(); long taskId = viewHolder.task.getId();
detailManager.reset(viewHolder, taskId);
decorationManager.reset(viewHolder, taskId); decorationManager.reset(viewHolder, taskId);
viewHolder.extendedDetails.setVisibility(View.GONE);
viewHolder.actions.setVisibility(View.GONE);
} }
} }
@ -348,9 +361,10 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
* created. * created.
*/ */
private void addListeners(final View container) { private void addListeners(final View container) {
ViewHolder viewHolder = (ViewHolder)container.getTag();
// check box listener // check box listener
final CheckBox completeBox = ((CheckBox)container.findViewById(R.id.completeBox)); viewHolder.completeBox.setOnClickListener(completeBoxListener);
completeBox.setOnClickListener(completeBoxListener);
// context menu listener // context menu listener
container.setOnCreateContextMenuListener(listener); container.setOnCreateContextMenuListener(listener);
@ -360,27 +374,81 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
} }
/* ====================================================================== /* ======================================================================
* ============================================================== add-ons * ============================================================== details
* ====================================================================== */ * ====================================================================== */
/** // implementation note: this map is really costly if users have
* Called to tell the cache to be cleared // 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
public void flushCaches() { private final Map<Long, StringBuilder> taskDetailLoader = Collections.synchronizedMap(new HashMap<Long, StringBuilder>());
detailManager.clearCache();
extendedDetailManager.clearCache(); private final Task taskDetailContainer = new Task();
decorationManager.clearCache();
taskActionManager.clearCache(); public class DetailLoaderThread extends Thread {
@Override
public void run() {
// for all of the tasks returned by our cursor, verify details
TodorooCursor<Task> fetchCursor = taskService.fetchFiltered(
query.get(), null, Task.ID, Task.DETAILS, Task.COMPLETION_DATE);
activity.startManagingCursor(fetchCursor);
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
}
}
} }
/** /**
* AddOnManager for Details * Add detail to a task
* @author Tim Su <tim@todoroo.com>
* *
* @param id
* @param detail
*/ */
public class DetailManager extends AddOnManager<String> { public void addDetails(long id, String detail) {
final StringBuilder details = taskDetailLoader.get(id);
if(details == null)
return;
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);
}
private final ImageGetter imageGetter = new ImageGetter() { activity.runOnUiThread(new Runnable() {
@Override
public void run() {
ListView listView = activity.getListView();
int scrollPos = listView.getScrollY();
notifyDataSetInvalidated();
listView.scrollTo(0, scrollPos);
}
});
}
private final ImageGetter detailImageGetter = new ImageGetter() {
public Drawable getDrawable(String source) { public Drawable getDrawable(String source) {
Resources r = activity.getResources(); Resources r = activity.getResources();
int drawable = r.getIdentifier("drawable/" + source, null, Constants.PACKAGE); //$NON-NLS-1$ int drawable = r.getIdentifier("drawable/" + source, null, Constants.PACKAGE); //$NON-NLS-1$
@ -392,50 +460,54 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
} }
}; };
private final boolean extended; /* ======================================================================
public DetailManager(boolean extended) { * ============================================================== add-ons
this.extended = extended; * ====================================================================== */
/**
* Called to tell the cache to be cleared
*/
public void flushCaches() {
extendedDetailManager.clearCache();
decorationManager.clearCache();
taskActionManager.clearCache();
taskDetailLoader.clear();
detailLoader = new DetailLoaderThread();
detailLoader.start();
}
/**
* Called to tell the cache to be cleared
*/
public void flushSpecific(long taskId) {
extendedDetailManager.clearCache(taskId);
decorationManager.clearCache(taskId);
taskActionManager.clearCache(taskId);
taskDetailLoader.remove(taskId);
}
/**
* AddOnManager for Details
* @author Tim Su <tim@todoroo.com>
*
*/
public class DetailManager extends AddOnManager<String> {
public DetailManager() {
//
} }
@Override @Override
Intent createBroadcastIntent(long taskId) { Intent createBroadcastIntent(Task task) {
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DETAILS); Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DETAILS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId());
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, extended); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, true);
return broadcastIntent; return broadcastIntent;
} }
@Override @Override
public boolean request(final ViewHolder viewHolder) { public void addNew(long taskId, String addOn, String item) {
if(super.request(viewHolder)) { super.addNew(taskId, addOn, item);
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<String> 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;
} }
@SuppressWarnings("nls") @SuppressWarnings("nls")
@ -443,9 +515,9 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
void draw(ViewHolder viewHolder, long taskId, Collection<String> details) { void draw(ViewHolder viewHolder, long taskId, Collection<String> details) {
if(details == null || viewHolder.task.getId() != taskId) if(details == null || viewHolder.task.getId() != taskId)
return; return;
TextView view = extended ? viewHolder.extendedDetails : viewHolder.details; TextView view = viewHolder.extendedDetails;
if(details.isEmpty() || (expanded != taskId)) {
reset(viewHolder, taskId); reset(viewHolder, taskId);
if(details.isEmpty() || (extended && expanded != taskId)) {
return; return;
} }
view.setVisibility(View.VISIBLE); view.setVisibility(View.VISIBLE);
@ -457,7 +529,8 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
} }
String string = detailText.toString(); String string = detailText.toString();
if(string.contains("<")) if(string.contains("<"))
view.setText(Html.fromHtml(string.trim().replace("\n", "<br>"), imageGetter, null)); view.setText(Html.fromHtml(string.trim().replace("\n", "<br>"),
detailImageGetter, null));
else else
view.setText(string.trim()); view.setText(string.trim());
Linkify.addLinks(view, Linkify.ALL); Linkify.addLinks(view, Linkify.ALL);
@ -465,7 +538,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
@Override @Override
void reset(ViewHolder viewHolder, long taskId) { void reset(ViewHolder viewHolder, long taskId) {
TextView view = extended ? viewHolder.extendedDetails : viewHolder.details; TextView view = viewHolder.extendedDetails;
view.setVisibility(View.GONE); view.setVisibility(View.GONE);
} }
} }
@ -478,12 +551,18 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
*/ */
public class DecorationManager extends AddOnManager<TaskDecoration> { public class DecorationManager extends AddOnManager<TaskDecoration> {
@Override @Override
Intent createBroadcastIntent(long taskId) { Intent createBroadcastIntent(Task task) {
Intent intent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DECORATIONS); Intent intent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DECORATIONS);
intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId());
intent.putExtra(BROADCAST_EXTRA_TASK, task);
return intent; return intent;
} }
@Override
public void addNew(long taskId, String addOn, TaskDecoration item) {
super.addNew(taskId, addOn, item);
}
@Override @Override
void draw(ViewHolder viewHolder, long taskId, Collection<TaskDecoration> decorations) { void draw(ViewHolder viewHolder, long taskId, Collection<TaskDecoration> decorations) {
if(decorations == null || viewHolder.task.getId() != taskId) if(decorations == null || viewHolder.task.getId() != taskId)
@ -493,6 +572,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
if(decorations.size() == 0) if(decorations.size() == 0)
return; return;
int i = 0; int i = 0;
boolean colorSet = false; boolean colorSet = false;
viewHolder.decorations = new View[decorations.size()]; viewHolder.decorations = new View[decorations.size()];
@ -522,6 +602,9 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
for(View view : viewHolder.decorations) for(View view : viewHolder.decorations)
viewHolder.taskRow.removeView(view); viewHolder.taskRow.removeView(view);
} }
if(taskId == expanded)
viewHolder.view.setBackgroundColor(Color.argb(20, 255, 255, 255));
else
viewHolder.view.setBackgroundResource(android.R.drawable.list_selector_background); viewHolder.view.setBackgroundResource(android.R.drawable.list_selector_background);
} }
} }
@ -533,10 +616,15 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
* *
*/ */
public class TaskActionManager extends AddOnManager<TaskAction> { public class TaskActionManager extends AddOnManager<TaskAction> {
private final LinearLayout.LayoutParams params =
new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT, 1f);
@Override @Override
Intent createBroadcastIntent(long taskId) { Intent createBroadcastIntent(Task task) {
Intent intent = new Intent(AstridApiConstants.BROADCAST_REQUEST_ACTIONS); Intent intent = new Intent(AstridApiConstants.BROADCAST_REQUEST_ACTIONS);
intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId());
return intent; return intent;
} }
@ -545,15 +633,25 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
if(actions == null || viewHolder.task.getId() != taskId) if(actions == null || viewHolder.task.getId() != taskId)
return; return;
reset(viewHolder, taskId); // hack because we know we have > 1 button
if(addOnService.hasPowerPack() && actions.size() == 0)
LinearLayout.LayoutParams params = return;
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); Button editButton = new Button(activity);
editButton.setText(R.string.TAd_actionEditTask); editButton.setLayoutParams(params);
editButton.setOnClickListener(new OnClickListener() { viewHolder.actions.addView(editButton);
}
for(int i = actions.size() + 1; i < viewHolder.actions.getChildCount(); i++) {
viewHolder.actions.getChildAt(i).setVisibility(View.GONE);
}
int i = 0;
Button button = (Button) viewHolder.actions.getChildAt(i++);
button.setText(R.string.TAd_actionEditTask);
button.setVisibility(View.VISIBLE);
button.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View arg0) { public void onClick(View arg0) {
Intent intent = new Intent(activity, TaskEditActivity.class); Intent intent = new Intent(activity, TaskEditActivity.class);
@ -561,17 +659,15 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
activity.startActivityForResult(intent, TaskListActivity.ACTIVITY_EDIT_TASK); activity.startActivityForResult(intent, TaskListActivity.ACTIVITY_EDIT_TASK);
} }
}); });
editButton.setLayoutParams(params);
viewHolder.actions.addView(editButton);
for(TaskAction action : actions) { for(TaskAction action : actions) {
Button view = new Button(activity); button = (Button) viewHolder.actions.getChildAt(i++);
view.setText(action.text); button.setText(action.text);
view.setOnClickListener(new ActionClickListener(action)); button.setVisibility(View.VISIBLE);
view.setLayoutParams(params); button.setOnClickListener(new ActionClickListener(action, viewHolder));
viewHolder.actions.addView(view);
} }
reset(viewHolder, taskId);
} }
@Override @Override
@ -581,7 +677,6 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
return; return;
} }
viewHolder.actions.setVisibility(View.VISIBLE); viewHolder.actions.setVisibility(View.VISIBLE);
viewHolder.actions.removeAllViews();
} }
} }
@ -609,19 +704,25 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
}; };
private final class ActionClickListener implements View.OnClickListener { private final class ActionClickListener implements View.OnClickListener {
TaskAction action; private final TaskAction action;
private final ViewHolder viewHolder;
public ActionClickListener(TaskAction action) { public ActionClickListener(TaskAction action, ViewHolder viewHolder) {
this.action = action; this.action = action;
this.viewHolder = viewHolder;
} }
public void onClick(View v) { public void onClick(View v) {
flushSpecific(viewHolder.task.getId());
try { try {
action.intent.send(); action.intent.send();
} catch (Exception e) { } catch (Exception e) {
exceptionService.displayAndReportError(activity, exceptionService.displayAndReportError(activity,
"Error launching action", e); //$NON-NLS-1$ "Error launching action", e); //$NON-NLS-1$
} }
decorationManager.request(viewHolder);
extendedDetailManager.request(viewHolder);
taskActionManager.request(viewHolder);
} }
}; };
@ -718,7 +819,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
abstract public class AddOnManager<TYPE> { abstract public class AddOnManager<TYPE> {
private final Map<Long, HashMap<String, TYPE>> cache = private final Map<Long, HashMap<String, TYPE>> cache =
new SoftHashMap<Long, HashMap<String, TYPE>>(); new HashMap<Long, HashMap<String, TYPE>>();
// --- interface // --- interface
@ -737,13 +838,13 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
// request details // request details
draw(viewHolder, taskId, get(taskId)); draw(viewHolder, taskId, get(taskId));
Intent broadcastIntent = createBroadcastIntent(taskId); Intent broadcastIntent = createBroadcastIntent(viewHolder.task);
activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
return true; return true;
} }
/** creates a broadcast intent for requesting */ /** creates a broadcast intent for requesting */
abstract Intent createBroadcastIntent(long taskId); abstract Intent createBroadcastIntent(Task task);
/** updates the given view */ /** updates the given view */
abstract void draw(ViewHolder viewHolder, long taskId, Collection<TYPE> list); abstract void draw(ViewHolder viewHolder, long taskId, Collection<TYPE> list);
@ -778,6 +879,13 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
cache.clear(); cache.clear();
} }
/**
* Clears single item from cache
*/
public void clearCache(long taskId) {
cache.remove(taskId);
}
// --- internal goodies // --- internal goodies
/** /**

@ -1,26 +0,0 @@
package com.todoroo.astrid.api;
import android.content.Context;
/**
* Internal API for Task Details
*
* @author Tim Su <tim@todoroo.com>
*
*/
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();
}

@ -28,7 +28,7 @@ public class Database extends AbstractDatabase {
* Database version number. This variable must be updated when database * Database version number. This variable must be updated when database
* tables are updated, as it determines whether a database needs updating. * 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) * Database name (must be unique)
@ -119,6 +119,10 @@ public class Database extends AbstractDatabase {
append(')'); append(')');
database.execSQL(sql.toString()); database.execSQL(sql.toString());
} }
case 4: {
database.execSQL("ALTER TABLE " + Task.TABLE.name + " ADD " +
Task.DETAILS.accept(visitor, null));
}
return true; return true;
} }

@ -157,6 +157,10 @@ public class TaskDao extends GenericDao<Task> {
return false; return false;
} }
// clear task detail cache
if(values != null && !values.containsKey(Task.DETAILS.name))
values.put(Task.DETAILS.name, ""); //$NON-NLS-1$
if (task.getId() == Task.NO_ID) { if (task.getId() == Task.NO_ID) {
saveSuccessful = createNew(task); saveSuccessful = createNew(task);
} else { } else {

@ -14,11 +14,11 @@ import android.content.res.Resources;
import com.timsu.astrid.R; import com.timsu.astrid.R;
import com.todoroo.andlib.data.AbstractModel; import com.todoroo.andlib.data.AbstractModel;
import com.todoroo.andlib.data.Property; 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.IntegerProperty;
import com.todoroo.andlib.data.Property.LongProperty; import com.todoroo.andlib.data.Property.LongProperty;
import com.todoroo.andlib.data.Property.StringProperty; 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; import com.todoroo.andlib.utility.DateUtilities;
/** /**
@ -72,7 +72,13 @@ public final class Task extends AbstractModel {
public static final LongProperty DELETION_DATE = new LongProperty( public static final LongProperty DELETION_DATE = new LongProperty(
TABLE, "deleted"); 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 // move these into the metadata table and treat them as plug-ins
public static final StringProperty NOTES = new StringProperty( public static final StringProperty NOTES = new StringProperty(
@ -179,6 +185,7 @@ public final class Task extends AbstractModel {
defaultValues.put(NOTES.name, ""); defaultValues.put(NOTES.name, "");
defaultValues.put(FLAGS.name, 0); defaultValues.put(FLAGS.name, 0);
defaultValues.put(TIMER_START.name, 0); defaultValues.put(TIMER_START.name, 0);
defaultValues.put(DETAILS.name, (String)null);
} }
@Override @Override
@ -208,7 +215,7 @@ public final class Task extends AbstractModel {
// --- parcelable helpers // --- parcelable helpers
private static final Creator<Task> CREATOR = new ModelCreator<Task>(Task.class); public static final Creator<Task> CREATOR = new ModelCreator<Task>(Task.class);
@Override @Override
protected Creator<? extends AbstractModel> getCreator() { protected Creator<? extends AbstractModel> getCreator() {
@ -391,6 +398,8 @@ public final class Task extends AbstractModel {
* Checks whether this due date has a due time or only a date * Checks whether this due date has a due time or only a date
*/ */
public boolean hasDueTime() { public boolean hasDueTime() {
if(!hasDueDate())
return false;
return hasDueTime(getValue(Task.DUE_DATE)); return hasDueTime(getValue(Task.DUE_DATE));
} }

@ -1,5 +1,7 @@
package com.todoroo.astrid.service; package com.todoroo.astrid.service;
import android.content.ContentValues;
import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.Autowired;
@ -12,8 +14,8 @@ import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.PermaSql; import com.todoroo.astrid.api.PermaSql;
import com.todoroo.astrid.dao.MetadataDao; import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.Task; import com.todoroo.astrid.model.Task;
@ -132,6 +134,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 * Clean up tasks. Typically called on startup
*/ */
@ -212,6 +223,18 @@ public class TaskService {
} }
} }
/**
* Clear details cache. Useful if user performs some operation that
* affects details
*
* @return # of affected rows
*/
public int clearDetails() {
ContentValues values = new ContentValues();
values.put(Task.DETAILS.name, (String) null);
return taskDao.updateMultiple(values, Criterion.all);
}
/** /**
* Update database based on selection and values * Update database based on selection and values
* @param selection * @param selection

@ -53,4 +53,7 @@ public final class Constants {
/** Notification Manager id for locale */ /** Notification Manager id for locale */
public static final int NOTIFICATION_LOCALE = -3; public static final int NOTIFICATION_LOCALE = -3;
/** Notification Manager id for producteev notifications*/
public static final int NOTIFICATION_PRODUCTEEV_NOTIFICATIONS = -4;
} }

Loading…
Cancel
Save