Added repeating tasks feature, help menu, and started work on RTM sync.

pull/14/head
Tim Su 16 years ago
parent fa4642ab3d
commit 5d07f907ee

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry excluding="javax/xml/validation/|javax/xml/transform/dom/|javax/xml/parsers/|org/jaxp/transform/dom/|org/jaxp/transform/sax/|org/jaxp/transform/stax/|org/jaxp/transform/stream/|org/jaxp/stream/events/|org/jaxp/stream/util/|org/jaxp/parsers/|org/jaxp/stream/|org/jaxp/validation/" kind="src" path="src"/>
<classpathentry kind="lib" path="lib/android-src.jar"/>
<classpathentry kind="lib" path="lib/android.jar" sourcepath="lib/android-src.jar"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="lib" path="lib/commons-codec-1.3.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.timsu.astrid"
android:versionCode="35"
android:versionName="1.9.9">
android:versionCode="37"
android:versionName="1.10.1">
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application android:icon="@drawable/icon" android:label="@string/app_name">

Binary file not shown.

@ -32,13 +32,13 @@
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:id="@+id/name_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/name_label"
style="@style/TextAppearance.EditEvent_Label"/>
<EditText android:id="@+id/name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
@ -73,6 +73,7 @@
<LinearLayout android:id="@+id/tags_container"
android:orientation="vertical"
android:paddingBottom="5dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</LinearLayout>
@ -80,10 +81,26 @@
<View
android:layout_width="fill_parent"
android:layout_height="1dip"
android:padding="5dip"
android:background="@android:drawable/divider_horizontal_dark"
/>
<TextView android:id="@+id/notes_label"
android:paddingTop="5dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/notes_label"
style="@style/TextAppearance.EditEvent_Label"/>
<EditText android:id="@+id/notes"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:scrollbars="vertical"
android:gravity="top"
android:hint="@string/notes_hint"
android:capitalize="sentences"
android:singleLine="false" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
@ -215,6 +232,31 @@
</LinearLayout>
<TextView android:id="@+id/repeat_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/repeat_label"
style="@style/TextAppearance.EditEvent_Label"/>
<LinearLayout
android:orientation="horizontal"
android:paddingRight="10dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<Button android:id="@+id/repeat_value"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Spinner android:id="@+id/repeat_interval"
android:layout_weight="1"
android:layout_marginRight="10dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="1dip"
@ -231,23 +273,6 @@
<Button android:id="@+id/elapsedDuration"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView android:id="@+id/notes_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/notes_label"
style="@style/TextAppearance.EditEvent_Label"/>
<EditText android:id="@+id/notes"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:scrollbars="vertical"
android:gravity="top"
android:hint="@string/notes_hint"
android:capitalize="sentences"
android:singleLine="false" />
</LinearLayout>
</ScrollView>

@ -32,6 +32,11 @@
<string name="importance_3">It would be nice to do this</string>
<string name="importance_4">Who cares!</string>
<!-- Repeat Interval Labels -->
<string name="repeat_days">Day(s)</string>
<string name="repeat_weeks">Week(s)</string>
<string name="repeat_months">Month(s)</string>
<!-- Plurals -->
<plurals name="Ntasks">
<item quantity="one">1 Task</item>
@ -81,8 +86,10 @@
<string name="taskList_menu_insert">Add</string>
<string name="taskList_menu_tags">Tags</string>
<string name="taskList_menu_settings">Settings</string>
<string name="taskList_menu_filters">Filters</string>
<string name="taskList_menu_more">More</string>
<string name="taskList_menu_settings">Settings</string>
<string name="taskList_menu_help">Help</string>
<string name="taskList_context_edit">Edit Task</string>
<string name="taskList_context_delete">Delete Task</string>
@ -105,19 +112,21 @@
<string name="task_edit_tab_3">Alerts</string>
<!-- labels -->
<string name="name_label">What</string>
<string name="name_label">Summary</string>
<string name="name_hint">Task Description</string>
<string name="importance_label">How Important is it?</string>
<string name="tags_label">Tags:</string>
<string name="notes_label">Notes</string>
<string name="notes_hint">Enter Task Notes</string>
<string name="estimatedDuration_label">How Long Will it Take?</string>
<string name="elapsedDuration_label">Time Already Spent on Task</string>
<string name="definiteDueDate_label">Absolute Deadline</string>
<string name="preferredDueDate_label">Goal Deadline</string>
<string name="hiddenUntil_label">Hide Until This Date</string>
<string name="repeat_label">Repeat Every</string>
<string name="repeat_value_unset">No Repeat Set</string>
<string name="blockingOn_label">Hide Until This Task is Done</string>
<string name="notes_label">Notes</string>
<string name="notes_hint">Enter Task Notes</string>
<string name="notification_label">Periodic Reminders</string>
<string name="notification_prefix">Every</string>
@ -131,6 +140,15 @@
<!-- dialog boxes -->
<string name="hour_minutes_dialog">Time (hours : minutes)</string>
<string name="notification_dialog">Remind Me Every</string>
<string name="repeat_picker_title">Repeat Every (0 to disable)</string>
<string name="repeat_help_dialog_title">Help: Astrid Repeats</string>
<string name="repeat_help_dialog">
To use repeats, set at least one of the deadlines above. When you complete this task, the deadline will be automatically advanced.
\n\n
If you don\'t want to see the new task right after you complete the old one, you should use the "Hide Until" field, which will also be advanced automatically.
\n
</string>
<string name="repeat_help_hide">Don't Show Help Anymore</string>
<!-- buttons -->
<string name="save_label">Save</string>
@ -212,7 +230,10 @@
<string name="prefs_quietEnd_title">Quiet Hours End</string>
<string name="prefs_quietEnd_desc">Ending hour when Astrid should be quiet (e.g. 08)</string>
<string name="prefs_annoy_title">Persistence Mode</string>
<string name="prefs_annoy_desc">If checked, Astrid will try harder to get your attention.</string>
<string name="prefs_annoy_desc">
If checked, Astrid will try harder to get your attention:
- Must view reminders before hiding them
</string>
<string name="prefs_notification_title">Notification Ringtone</string>
<string name="prefs_notification_desc">Choose how Astrid alerts you!</string>
</resources>

@ -0,0 +1,74 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm;
/**
* Encapsulates information about an application that is a client of RememberTheMilk. Includes information required by RTM to connect: the API key and
* the shared secret.
*
* @author Will Ross Jun 22, 2007
*/
public class ApplicationInfo
{
private final String apiKey;
private final String sharedSecret;
private final String name;
private final String authToken;
public ApplicationInfo(String apiKey, String sharedSecret, String name)
{
this(apiKey, sharedSecret, name, null);
}
public ApplicationInfo(String apiKey, String sharedSecret, String name,
String authToken)
{
super();
this.apiKey = apiKey;
this.sharedSecret = sharedSecret;
this.name = name;
this.authToken = authToken;
}
public String getApiKey()
{
return apiKey;
}
public String getSharedSecret()
{
return sharedSecret;
}
public String getName()
{
return name;
}
public String getAuthToken()
{
return authToken;
}
}

@ -0,0 +1,414 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpClientConnection;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.RequestConnControl;
import org.apache.http.protocol.RequestContent;
import org.apache.http.protocol.RequestExpectContinue;
import org.apache.http.protocol.RequestTargetHost;
import org.apache.http.protocol.RequestUserAgent;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import android.util.Log;
/**
* Handles the details of invoking a method on the RTM REST API.
*
* @author Will Ross Jun 21, 2007
*/
public class Invoker {
private static final String TAG = "rtm-invoker";
private static final DocumentBuilder builder;
static
{
// Done this way because the builder is marked "final"
DocumentBuilder aBuilder;
try
{
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(false);
factory.setValidating(false);
aBuilder = factory.newDocumentBuilder();
}
catch (Exception exception)
{
Log.e(TAG, "Unable to construct a document builder", exception);
aBuilder = null;
}
builder = aBuilder;
}
public static final String REST_SERVICE_URL_POSTFIX = "/services/rest/";
public static final String ENCODING = "UTF-8";
public static String API_SIG_PARAM = "api_sig";
public static final long INVOCATION_INTERVAL = 2000;
private long lastInvocation;
private final ApplicationInfo applicationInfo;
private final MessageDigest digest;
private String proxyHostName;
private int proxyPortNumber;
private String proxyLogin;
private String proxyPassword;
private String serviceRelativeUri;
private HttpHost host;
private HttpContext context;
private BasicHttpParams globalHttpParams;
private DefaultConnectionReuseStrategy connectionStrategy;
private BasicHttpProcessor httpProcessor;
private HttpClient httpClient;
private DefaultHttpClientConnection connection;
public Invoker(String serverHostName, int serverPortNumber, String serviceRelativeUri, ApplicationInfo applicationInfo)
throws ServiceInternalException
{
this.serviceRelativeUri = serviceRelativeUri;
host = new HttpHost(serverHostName, serverPortNumber);
context = new BasicHttpContext();
globalHttpParams = new BasicHttpParams();
HttpProtocolParams.setVersion(globalHttpParams, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(globalHttpParams, ENCODING);
HttpProtocolParams.setUserAgent(globalHttpParams, "Jakarta-HttpComponents/1.1");
HttpProtocolParams.setUseExpectContinue(globalHttpParams, true);
connectionStrategy = new DefaultConnectionReuseStrategy();
httpProcessor = new BasicHttpProcessor();
// Required protocol interceptors
httpProcessor.addInterceptor(new RequestContent());
httpProcessor.addInterceptor(new RequestTargetHost());
// Recommended protocol interceptors
httpProcessor.addInterceptor(new RequestConnControl());
httpProcessor.addInterceptor(new RequestUserAgent());
httpProcessor.addInterceptor(new RequestExpectContinue());
httpClient = new DefaultHttpClient();
lastInvocation = System.currentTimeMillis();
this.applicationInfo = applicationInfo;
try
{
digest = MessageDigest.getInstance("md5");
}
catch (NoSuchAlgorithmException e)
{
throw new ServiceInternalException("Could not create properly the MD5 digest", e);
}
}
public void setHttpProxySettings(String proxyHostName, int proxyPortNumber, String proxyLogin, String proxyPassword)
{
this.proxyHostName = proxyHostName;
this.proxyPortNumber = proxyPortNumber;
this.proxyLogin = proxyLogin;
this.proxyPassword = proxyPassword;
}
private void prepareConnection()
throws ServiceInternalException
{
connection = new DefaultHttpClientConnection();
// We open the necessary socket connection
try
{
if (connection.isOpen() == false)
{
final Socket socket = new Socket(host.getHostName(), host.getPort());
connection.bind(socket, globalHttpParams);
}
}
catch (Exception exception)
{
final StringBuffer message = new StringBuffer("Cannot open a socket connection to '").append(host.getHostName()).append("' on port number ").append(
host.getPort()).append(": cannot execute query");
Log.e(TAG, message.toString(), exception);
throw new ServiceInternalException(message.toString());
}
}
private StringBuffer computeRequestUri(Param... params)
throws ServiceInternalException
{
final StringBuffer requestUri = new StringBuffer(serviceRelativeUri);
if (params.length > 0)
{
requestUri.append("?");
}
for (Param param : params)
{
try
{
requestUri.append(param.getName()).append("=").append(URLEncoder.encode(param.getValue(), ENCODING)).append("&");
}
catch (Exception exception)
{
final StringBuffer message = new StringBuffer("Cannot encode properly the HTTP GET request URI: cannot execute query");
Log.e(TAG, message.toString(), exception);
throw new ServiceInternalException(message.toString());
}
}
requestUri.append(API_SIG_PARAM).append("=").append(calcApiSig(params));
return requestUri;
}
public Element invoke(Param... params)
throws ServiceException
{
long timeSinceLastInvocation = System.currentTimeMillis() - lastInvocation;
if (timeSinceLastInvocation < INVOCATION_INTERVAL)
{
// In order not to invoke the RTM service too often
try
{
Thread.sleep(INVOCATION_INTERVAL - timeSinceLastInvocation);
}
catch (InterruptedException e)
{
throw new ServiceInternalException("Unexpected interruption while attempting to pause for some time before invoking the RTM service back", e);
}
}
Log.d(TAG, "Invoker running at " + new Date());
// We prepare the network socket-based connection
//prepareConnection();
// We compute the URI
final StringBuffer requestUri = computeRequestUri(params);
HttpResponse response = null;
final HttpGet request = new HttpGet("http://" + ServiceImpl.SERVER_HOST_NAME + requestUri.toString());
request.setHeader(new BasicHeader(HTTP.CHARSET_PARAM, ENCODING));
final String methodUri = request.getRequestLine().getUri();
// TODO: put that back!
// if (proxyHostName != null)
// {
// // Sets an HTTP proxy and the credentials for authentication
// client.getHostConfiguration().setProxy(proxyHostName, proxyPortNumber);
// if (proxyLogin != null)
// {
// client.getState().setProxyCredentials(AuthScope.ANY, new UsernamePasswordCredentials(proxyLogin, proxyPassword));
// }
// }
Element result;
try
{
Log.i(TAG, "Executing the method:" + methodUri);
response = httpClient.execute(request);
// httpExecutor.execute(request, connection, context);
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK)
{
Log.e(TAG, "Method failed: " + response.getStatusLine());
throw new ServiceInternalException("method failed: " + response.getStatusLine());
}
// THINK: this method is deprecated, but the only way to get the body as a string, without consuming
// the body input stream: the HttpMethodBase issues a warning but does not let you call the "setResponseStream()" method!
final String responseBodyAsString = "";//EntityUtils.toString(response.getEntity());
Log.i(TAG, " Invocation response:\r\n" + responseBodyAsString);
final Document responseDoc = builder.parse(response.getEntity().getContent());
final Element wrapperElt = responseDoc.getDocumentElement();
if (!wrapperElt.getNodeName().equals("rsp"))
{
throw new ServiceInternalException("unexpected response returned by RTM service: " + responseBodyAsString);
}
else
{
String stat = wrapperElt.getAttribute("stat");
if (stat.equals("fail"))
{
Node errElt = wrapperElt.getFirstChild();
while (errElt != null && (errElt.getNodeType() != Node.ELEMENT_NODE || !errElt.getNodeName().equals("err")))
{
errElt = errElt.getNextSibling();
}
if (errElt == null)
{
throw new ServiceInternalException("unexpected response returned by RTM service: " + responseBodyAsString);
}
else
{
throw new ServiceException(Integer.parseInt(((Element) errElt).getAttribute("code")), ((Element) errElt).getAttribute("msg"));
}
}
else
{
Node dataElt = wrapperElt.getFirstChild();
while (dataElt != null && (dataElt.getNodeType() != Node.ELEMENT_NODE || dataElt.getNodeName().equals("transaction") == true))
{
try
{
Node nextSibling = dataElt.getNextSibling();
if (nextSibling == null)
{
break;
}
else
{
dataElt = nextSibling;
}
}
catch (IndexOutOfBoundsException exception)
{
// Some implementation may throw this exception, instead of returning a null sibling
break;
}
}
if (dataElt == null)
{
throw new ServiceInternalException("unexpected response returned by RTM service: " + responseBodyAsString);
}
else
{
result = (Element) dataElt;
}
}
}
}
catch (IOException e)
{
throw new ServiceInternalException("", e);
}
catch (SAXException e)
{
throw new ServiceInternalException("", e);
}
// catch (HttpException e)
// {
// throw new ServiceInternalException("", e);
// }
finally
{
if (connection != null && (response == null || connectionStrategy.keepAlive(response, context) == false))
{
try
{
connection.close();
}
catch (IOException exception)
{
Log.w(TAG, new StringBuffer("Could not close properly the socket connection to '").append(connection.getRemoteAddress()).append("' on port ").append(
connection.getRemotePort()).toString(), exception);
}
}
}
lastInvocation = System.currentTimeMillis();
return result;
}
final String calcApiSig(Param... params)
throws ServiceInternalException
{
try
{
digest.reset();
digest.update(applicationInfo.getSharedSecret().getBytes(ENCODING));
List<Param> sorted = Arrays.asList(params);
Collections.sort(sorted);
for (Param param : sorted)
{
digest.update(param.getName().getBytes(ENCODING));
digest.update(param.getValue().getBytes(ENCODING));
}
return convertToHex(digest.digest());
}
catch (UnsupportedEncodingException e)
{
throw new ServiceInternalException("cannot hahdle properly the encoding", e);
}
}
private static String convertToHex(byte[] data)
{
StringBuffer buf = new StringBuffer();
for (int i = 0; i < data.length; i++)
{
int halfbyte = (data[i] >>> 4) & 0x0F;
int two_halfs = 0;
do
{
if ((0 <= halfbyte) && (halfbyte <= 9))
buf.append((char) ('0' + halfbyte));
else
buf.append((char) ('a' + (halfbyte - 10)));
halfbyte = data[i] & 0x0F;
}
while (two_halfs++ < 1);
}
return buf.toString();
}
}

@ -0,0 +1,64 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm;
import java.util.Date;
import com.mdt.rtm.data.RtmData;
/**
*
* @author Will Ross Jun 21, 2007
*/
public class Param
implements Comparable<Param>
{
private final String name;
private final String value;
public Param(String name, String value)
{
this.name = name;
this.value = value;
}
public Param(String name, Date value)
{
this.name = name;
this.value = RtmData.formatDate(value);
}
public String getName()
{
return name;
}
public String getValue()
{
return value;
}
public int compareTo(Param p)
{
return name.compareTo(p.getName());
}
}

@ -0,0 +1,47 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm;
import java.util.prefs.Preferences;
/**
*
* @author Will Ross Jun 21, 2007
*/
public class Prefs {
Preferences preferences;
public enum PrefKey {
AuthToken
}
public Prefs() {
preferences = Preferences.userNodeForPackage(Prefs.class);
}
public String getAuthToken() {
return preferences.get(PrefKey.AuthToken.toString(), null);
}
public void setAuthToken(String authToken) {
preferences.put(PrefKey.AuthToken.toString(), authToken);
}
}

@ -0,0 +1,262 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import com.mdt.rtm.data.RtmAuth;
import com.mdt.rtm.data.RtmFrob;
import com.mdt.rtm.data.RtmList;
import com.mdt.rtm.data.RtmLists;
import com.mdt.rtm.data.RtmLocation;
import com.mdt.rtm.data.RtmTaskNote;
import com.mdt.rtm.data.RtmTaskSeries;
import com.mdt.rtm.data.RtmTasks;
import com.mdt.rtm.data.RtmTask.Priority;
/**
* Represents the Remember the Milk service API.
*
* @author Will Ross Jun 21, 2007
*/
public interface Service
{
/**
* Checks whether the service is authorized to communicate with the RTM server. Depends on the user's login info, and whether or not that user has
* authorized the service wrapper to communicate with RTM.
*
* @return true if the service API has permission to interact with full permissions (including delete) with RTM
* @throws ServiceException
* if there is a problem checking for authorization
*/
boolean isServiceAuthorized()
throws ServiceException;
/**
* Begins the process of obtaining authorization for the service API to communicate with RTM on behalf of a particular user.
*
* @return the URL that the user should be prompted to log in to to complete authorization
* @throws ServiceException
* if the authorization process cannot be started
*/
String beginAuthorization(RtmAuth.Perms permissions)
throws ServiceException;
/**
* The same method as the previous {@link #beginAuthorization(com.mdt.rtm.data.RtmAuth.Perms)}, except that you need to invoke yourself the
* {@link #auth_getFrob()} beforehand.
*
* This has been introduced, in order to provide better control over the API.
*/
String beginAuthorization(RtmFrob frob, RtmAuth.Perms permissions)
throws ServiceException;
/**
* Completes the process of obtaining authorization for the service API to communicate with RTM on behalf of a particular user.
*
* Once this is called successfully, <code>isServiceAuthorized()</code> should return true until the user goes to RTM and explicitly denies the
* service access. It also might be possible for authorization to time out, in which case this process would need to be started again.
*
* @return the newly created authentication token
* @throws ServiceException
* if the authorization process cannot be completed
*/
String completeAuthorization()
throws ServiceException;
/**
* Same as the previous {@link #completeAuthorization()} method, except that the frob taken is implicitly given. Very useful when you need to handle
* multiple authentication tokens.
*/
String completeAuthorization(RtmFrob frob)
throws ServiceException;
RtmAuth auth_checkToken(String authToken)
throws ServiceException;
RtmFrob auth_getFrob()
throws ServiceException;
String auth_getToken(String frob)
throws ServiceException;
void contacts_add()
throws ServiceException;
void contacts_delete()
throws ServiceException;
void contacts_getList()
throws ServiceException;
void groups_add()
throws ServiceException;
void groups_addContact()
throws ServiceException;
void groups_delete()
throws ServiceException;
void groups_getList()
throws ServiceException;
void groups_removeContact()
throws ServiceException;
RtmList lists_add(String timelineId, String listName)
throws ServiceException;
void lists_archive()
throws ServiceException;
void lists_delete(String timelineId, String listId)
throws ServiceException;
RtmLists lists_getList()
throws ServiceException;
RtmList lists_getList(String listName)
throws ServiceException;
void lists_setDefaultList()
throws ServiceException;
RtmList lists_setName(String timelineId, String listId, String newName)
throws ServiceException;
void lists_unarchive()
throws ServiceException;
void reflection_getMethodInfo()
throws ServiceException;
void reflection_getMethods()
throws ServiceException;
void settings_getList()
throws ServiceException;
RtmTaskSeries tasks_add(String timelineId, String listId, String name)
throws ServiceException;
void tasks_addTags()
throws ServiceException;
public RtmTaskSeries tasks_complete(String timelineId, String listId, String taskSeriesId, String taskId)
throws ServiceException;
void tasks_delete(String timelineId, String listId, String taskSeriesId, String taskId)
throws ServiceException;
RtmTasks tasks_getList(String listId, String filter, Date lastSync)
throws ServiceException;
RtmTaskSeries tasks_getTask(String taskId, String taskName)
throws ServiceException;
/**
* @return Warning: the very first task with the given name is returned!
*/
RtmTaskSeries tasks_getTask(String taskName)
throws ServiceException;
void tasks_movePriority()
throws ServiceException;
RtmTaskSeries tasks_moveTo(String timelineId, String fromListId, String toListId, String taskSeriesId, String taskId)
throws ServiceException;
void tasks_postpone()
throws ServiceException;
void tasks_removeTags()
throws ServiceException;
/**
* THINK: Would it not be better to have a {@link GregorianCalendar} parameter instead?
*/
RtmTaskSeries tasks_setDueDate(String timelineId, String listId, String taskSeriesId, String taskId, Date due, boolean hasDueTime)
throws ServiceException;
void tasks_setEstimate()
throws ServiceException;
RtmTaskSeries tasks_setName(String timelineId, String listId, String taskSeriesId, String taskId, String newName)
throws ServiceException;
RtmTaskSeries tasks_setPriority(String timelineId, String listId, String taskSeriesId, String taskId, Priority priority)
throws ServiceException;
void tasks_setRecurrence()
throws ServiceException;
void tasks_setTags()
throws ServiceException;
void tasks_setURL()
throws ServiceException;
RtmTaskSeries tasks_uncomplete(String timelineId, String listId, String taskSeriesId, String taskId)
throws ServiceException;
RtmTaskNote tasks_notes_add(String timelineId, String listId, String taskSeriesId, String taskId, String title, String text)
throws ServiceException;
void tasks_notes_delete(String timelineId, String noteId)
throws ServiceException;
RtmTaskNote tasks_notes_edit(String timelineId, String noteId, String title, String text)
throws ServiceException;
RtmTaskSeries tasks_setLocation(String timelineId, String listId, String taskSeriesId, String taskId, String locationId)
throws ServiceException;
RtmTaskSeries tasks_setURL(String timelineId, String listId, String taskSeriesId, String taskId, String url)
throws ServiceException;
void test_echo()
throws ServiceException;
void test_login()
throws ServiceException;
void time_convert()
throws ServiceException;
void time_parse()
throws ServiceException;
String timelines_create()
throws ServiceException;
void timezones_getList()
throws ServiceException;
void transactions_undo()
throws ServiceException;
List<RtmLocation> locations_getList()
throws ServiceException;
}

@ -0,0 +1,47 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm;
/**
*
* @author Will Ross Jun 21, 2007
*/
public class ServiceException extends Exception {
private static final long serialVersionUID = -6711156026040643361L;
int responseCode;
String responseMessage;
public ServiceException(int responseCode, String responseMessage) {
super("Service invocation failed. Code: " + responseCode + "; message: " + responseMessage);
this.responseCode = responseCode;
this.responseMessage = responseMessage;
}
int getResponseCode() {
return responseCode;
}
String getResponseMessage() {
return responseMessage;
}
}

@ -0,0 +1,612 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;
import org.w3c.dom.Element;
import com.mdt.rtm.data.RtmAuth;
import com.mdt.rtm.data.RtmData;
import com.mdt.rtm.data.RtmFrob;
import com.mdt.rtm.data.RtmList;
import com.mdt.rtm.data.RtmLists;
import com.mdt.rtm.data.RtmLocation;
import com.mdt.rtm.data.RtmTask;
import com.mdt.rtm.data.RtmTaskList;
import com.mdt.rtm.data.RtmTaskNote;
import com.mdt.rtm.data.RtmTaskSeries;
import com.mdt.rtm.data.RtmTasks;
import com.mdt.rtm.data.RtmTimeline;
import com.mdt.rtm.data.RtmTask.Priority;
/**
* A major part of the RTM API implementation is here.
*
* @author Will Ross Jun 21, 2007
* @author Edouard Mercier, since 2008.04.15
*/
public class ServiceImpl
implements Service
{
public final static String SERVER_HOST_NAME = "api.rememberthemilk.com"; //"74.86.175.154"; // api.rememberthemilk.com
public final static int SERVER_PORT_NUMBER = 80;
public final static String REST_SERVICE_URL_POSTFIX = "/services/rest/";
private final ApplicationInfo applicationInfo;
private final Invoker invoker;
private final Prefs prefs;
private String currentAuthToken;
RtmFrob tempFrob;
public ServiceImpl(ApplicationInfo applicationInfo)
throws ServiceInternalException
{
invoker = new Invoker(SERVER_HOST_NAME, SERVER_PORT_NUMBER, REST_SERVICE_URL_POSTFIX, applicationInfo);
this.applicationInfo = applicationInfo;
prefs = new Prefs();
if (applicationInfo.getAuthToken() != null)
{
currentAuthToken = applicationInfo.getAuthToken();
}
else
{
currentAuthToken = prefs.getAuthToken();
}
}
/**
* If you want to go through an HTTP proxy.
*
* @param proxyHostName
* the host name of the HTTP proxy machine (if <code>null</code>, no proxy is set, and all parameters are ignored)
* @param proxyPortNumber
* the port number the HTTP proxy listens to
* @param proxyLogin
* the account identifier against the HTTP proxy: if set to <code>null</code>, no authentication will be performed and the HTTP is
* considered working anonymously, and the last <code>proxyPassword</code> parameter is not taken into account
* @param proxyPassword
* the previous identifier related password
*/
public void setHttpProxySettings(String proxyHostName, int proxyPortNumber, String proxyLogin, String proxyPassword)
{
invoker.setHttpProxySettings(proxyHostName, proxyPortNumber, proxyLogin, proxyPassword);
}
public boolean isServiceAuthorized()
throws ServiceException
{
if (currentAuthToken == null)
return false;
try
{
/* RtmAuth auth = */auth_checkToken(currentAuthToken);
return true;
}
catch (ServiceException e)
{
if (e.getResponseCode() != 98)
{
throw e;
}
else
{
// Bad token.
currentAuthToken = null;
return false;
}
}
}
public String beginAuthorization(RtmAuth.Perms permissions)
throws ServiceException
{
// Instructions from the "User authentication for desktop applications"
// section at http://www.rememberthemilk.com/services/api/authentication.rtm
tempFrob = auth_getFrob();
return beginAuthorization(tempFrob, permissions);
}
public String beginAuthorization(RtmFrob frob, RtmAuth.Perms permissions)
throws ServiceException
{
String authBaseUrl = "http://" + SERVER_HOST_NAME + "/services/auth/";
Param[] params = new Param[] { new Param("api_key", applicationInfo.getApiKey()), new Param("perms", permissions.toString()),
new Param("frob", frob.getValue()) };
Param sig = new Param("api_sig", invoker.calcApiSig(params));
StringBuilder authUrl = new StringBuilder(authBaseUrl);
authUrl.append("?");
for (Param param : params)
{
authUrl.append(param.getName()).append("=").append(param.getValue()).append("&");
}
authUrl.append(sig.getName()).append("=").append(sig.getValue());
return authUrl.toString();
}
public String completeAuthorization()
throws ServiceException
{
return completeAuthorization(tempFrob);
}
public String completeAuthorization(RtmFrob frob)
throws ServiceException
{
currentAuthToken = auth_getToken(frob.getValue());
prefs.setAuthToken(currentAuthToken);
return currentAuthToken;
}
public RtmAuth auth_checkToken(String authToken)
throws ServiceException
{
Element response = invoker.invoke(new Param("method", "rtm.auth.checkToken"), new Param("auth_token", authToken),
new Param("api_key", applicationInfo.getApiKey()));
return new RtmAuth(response);
}
public RtmFrob auth_getFrob()
throws ServiceException
{
return new RtmFrob(invoker.invoke(new Param("method", "rtm.auth.getFrob"), new Param("api_key", applicationInfo.getApiKey())));
}
public String auth_getToken(String frob)
throws ServiceException
{
Element response = invoker.invoke(new Param("method", "rtm.auth.getToken"), new Param("frob", frob), new Param("api_key", applicationInfo.getApiKey()));
return new RtmAuth(response).getToken();
}
public void contacts_add()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void contacts_delete()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void contacts_getList()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void groups_add()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void groups_addContact()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void groups_delete()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void groups_getList()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void groups_removeContact()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public RtmList lists_add(String timelineId, String listName)
throws ServiceException
{
Element response = invoker.invoke(new Param("method", "rtm.lists.add"), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()), new Param("name", listName), new Param("timeline", timelineId));
return new RtmList(response);
}
public void lists_archive()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void lists_delete(String timelineId, String listId)
throws ServiceException
{
invoker.invoke(new Param("method", "rtm.lists.delete"), new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()),
new Param("timeline", timelineId), new Param("list_id", listId));
}
public RtmLists lists_getList()
throws ServiceException
{
Element response = invoker.invoke(new Param("method", "rtm.lists.getList"), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));
return new RtmLists(response);
}
public RtmList lists_getList(String listName)
throws ServiceException
{
RtmLists fullList = lists_getList();
for (Entry<String, RtmList> entry : fullList.getLists().entrySet())
{
if (entry.getValue().getName().equals(listName))
{
return entry.getValue();
}
}
return null;
}
public void lists_setDefaultList()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public RtmList lists_setName(String timelineId, String listId, String newName)
throws ServiceException
{
Element response = invoker.invoke(new Param("method", "rtm.lists.setName"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("name", newName), new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
return new RtmList(response);
}
public void lists_unarchive()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void reflection_getMethodInfo()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void reflection_getMethods()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void settings_getList()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public RtmTaskSeries tasks_add(String timelineId, String listId, String name)
throws ServiceException
{
Element response = invoker.invoke(new Param("method", "rtm.tasks.add"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("name", name), new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
RtmTaskList rtmTaskList = new RtmTaskList(response);
if (rtmTaskList.getSeries().size() == 1)
{
return rtmTaskList.getSeries().get(0);
}
else if (rtmTaskList.getSeries().size() > 1)
{
throw new ServiceInternalException("Internal error: more that one task (" + rtmTaskList.getSeries().size() + ") has been created");
}
throw new ServiceInternalException("Internal error: no task has been created");
}
public void tasks_addTags()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public RtmTaskSeries tasks_complete(String timelineId, String listId, String taskSeriesId, String taskId)
throws ServiceException
{
Element response = invoker.invoke(new Param("method", "rtm.tasks.complete"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));
RtmTaskList rtmTaskList = new RtmTaskList(response);
return findTask(taskSeriesId, taskId, rtmTaskList);
}
public void tasks_delete(String timelineId, String listId, String taskSeriesId, String taskId)
throws ServiceException
{
invoker.invoke(new Param("method", "rtm.tasks.delete"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));
}
public RtmTasks tasks_getList(String listId, String filter, Date lastSync)
throws ServiceException
{
Set<Param> params = new HashSet<Param>();
params.add(new Param("method", "rtm.tasks.getList"));
if (listId != null)
{
params.add(new Param("list_id", listId));
}
if (filter != null)
{
params.add(new Param("filter", filter));
}
if (lastSync != null)
{
params.add(new Param("last_sync", lastSync));
}
params.add(new Param("auth_token", currentAuthToken));
params.add(new Param("api_key", applicationInfo.getApiKey()));
return new RtmTasks(invoker.invoke(params.toArray(new Param[params.size()])));
}
public RtmTaskSeries tasks_getTask(String taskName)
throws ServiceException
{
return tasks_getTask(null, taskName);
}
public RtmTaskSeries tasks_getTask(String taskId, String taskName)
throws ServiceException
{
Set<Param> params = new HashSet<Param>();
params.add(new Param("method", "rtm.tasks.getList"));
params.add(new Param("auth_token", currentAuthToken));
params.add(new Param("api_key", applicationInfo.getApiKey()));
params.add(new Param("filter", "name:" + taskName));
RtmTasks rtmTasks = new RtmTasks(invoker.invoke(params.toArray(new Param[params.size()])));
return findTask(taskId, rtmTasks);
}
private RtmTaskSeries findTask(String taskId, RtmTasks rtmTasks)
{
for (RtmTaskList list : rtmTasks.getLists())
{
for (RtmTaskSeries series : list.getSeries())
{
if (taskId != null)
{
if (series.getId().equals(taskId))
{
return series;
}
}
else
{
return series;
}
}
}
return null;
}
public void tasks_movePriority()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public RtmTaskSeries tasks_moveTo(String timelineId, String fromListId, String toListId, String taskSeriesId, String taskId)
throws ServiceException
{
Element elt = invoker.invoke(new Param("method", "rtm.tasks.moveTo"), new Param("timeline", timelineId), new Param("from_list_id", fromListId),
new Param("to_list_id", toListId), new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));
RtmTaskList rtmTaskList = new RtmTaskList(elt);
return findTask(taskSeriesId, taskId, rtmTaskList);
}
public void tasks_postpone()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void tasks_removeTags()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public RtmTaskSeries tasks_setDueDate(String timelineId, String listId, String taskSeriesId, String taskId, Date due, boolean hasDueTime)
throws ServiceException
{
final boolean setDueDate = (due != null);
final Element elt;
if (setDueDate == true)
{
elt = invoker.invoke(new Param("method", "rtm.tasks.setDueDate"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("due", due), new Param("has_due_time", hasDueTime ? "1" : "0"),
new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
}
else
{
elt = invoker.invoke(new Param("method", "rtm.tasks.setDueDate"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));
}
final RtmTaskList rtmTaskList = new RtmTaskList(elt);
return findTask(taskSeriesId, taskId, rtmTaskList);
}
public void tasks_setEstimate()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public RtmTaskSeries tasks_setName(String timelineId, String listId, String taskSeriesId, String taskId, String newName)
throws ServiceException
{
Element elt = invoker.invoke(new Param("method", "rtm.tasks.setName"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("name", newName), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));
RtmTaskList rtmTaskList = new RtmTaskList(elt);
return findTask(taskSeriesId, taskId, rtmTaskList);
}
private RtmTaskSeries findTask(String taskSeriesId, String taskId, RtmTaskList rtmTaskList)
{
for (RtmTaskSeries series : rtmTaskList.getSeries())
{
if (series.getId().equals(taskSeriesId) && series.getTask().getId().equals(taskId))
{
return series;
}
}
return null;
}
public RtmTaskSeries tasks_setPriority(String timelineId, String listId, String taskSeriesId, String taskId, Priority priority)
throws ServiceException
{
Element elt = invoker.invoke(new Param("method", "rtm.tasks.setPriority"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("priority", RtmTask.convertPriority(priority)),
new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
RtmTaskList rtmTaskList = new RtmTaskList(elt);
return findTask(taskSeriesId, taskId, rtmTaskList);
}
public void tasks_setRecurrence()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void tasks_setTags()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void tasks_setURL()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public RtmTaskSeries tasks_uncomplete(String timelineId, String listId, String taskSeriesId, String taskId)
throws ServiceException
{
Element response = invoker.invoke(new Param("method", "rtm.tasks.uncomplete"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));
RtmTaskList rtmTaskList = new RtmTaskList(response);
return findTask(taskSeriesId, taskId, rtmTaskList);
}
public RtmTaskNote tasks_notes_add(String timelineId, String listId, String taskSeriesId, String taskId, String title, String text)
throws ServiceException
{
Element elt = invoker.invoke(new Param("method", "rtm.tasks.notes.add"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("note_title", title), new Param("note_text", text),
new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
return new RtmTaskNote(elt);
}
public void tasks_notes_delete(String timelineId, String noteId)
throws ServiceException
{
invoker.invoke(new Param("method", "rtm.tasks.notes.delete"), new Param("timeline", timelineId), new Param("note_id", noteId),
new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
}
public RtmTaskNote tasks_notes_edit(String timelineId, String noteId, String title, String text)
throws ServiceException
{
Element elt = invoker.invoke(new Param("method", "rtm.tasks.notes.edit"), new Param("timeline", timelineId), new Param("note_id", noteId),
new Param("note_title", title), new Param("note_text", text), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));
return new RtmTaskNote(elt);
}
public RtmTaskSeries tasks_setLocation(String timelineId, String listId, String taskSeriesId, String taskId, String locationId)
throws ServiceException
{
Element response = invoker.invoke(new Param("method", "rtm.tasks.setLocation"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("location_id", locationId),
new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
RtmTaskList rtmTaskList = new RtmTaskList(response);
return findTask(taskSeriesId, taskId, rtmTaskList);
}
public RtmTaskSeries tasks_setURL(String timelineId, String listId, String taskSeriesId, String taskId, String url)
throws ServiceException
{
Element response = invoker.invoke(new Param("method", "rtm.tasks.setURL"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("url", url), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));
RtmTaskList rtmTaskList = new RtmTaskList(response);
return findTask(taskSeriesId, taskId, rtmTaskList);
}
public void test_echo()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void test_login()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void time_convert()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void time_parse()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public String timelines_create()
throws ServiceException
{
return new RtmTimeline(invoker.invoke(new Param("method", "rtm.timelines.create"), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()))).getId();
}
public void timezones_getList()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public void transactions_undo()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public List<RtmLocation> locations_getList()
throws ServiceException
{
Element result = invoker.invoke(new Param("method", "rtm.locations.getList"), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));
List<RtmLocation> locations = new ArrayList<RtmLocation>();
for (Element child : RtmData.children(result, "location"))
{
locations.add(new RtmLocation(child));
}
return locations;
}
}

@ -0,0 +1,53 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm;
/**
* Introduced in order to get rid of the {@link RuntimeException}, and have only one time of regular exception to cope with, from the API end-user
* point of view.
*
* @author Edouard Mercier
* @since 2008.04.23
*/
public class ServiceInternalException
extends ServiceException
{
private static final long serialVersionUID = -423838945284984432L;
private final Exception enclosedException;
public ServiceInternalException(String message)
{
this(message, null);
}
public ServiceInternalException(String message, Exception exception)
{
super(-1, "Service internal exception: " + message);
this.enclosedException = exception;
}
public Exception getEnclosedException()
{
return enclosedException;
}
}

@ -0,0 +1,65 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm.data;
import org.w3c.dom.Element;
/**
*
* @author Will Ross Jun 21, 2007
*/
public class RtmAuth extends RtmData {
public enum Perms {
read, write, delete
}
private final String token;
private final Perms perms;
private final RtmUser user;
public RtmAuth(String token, Perms perms, RtmUser user) {
this.token = token;
this.perms = perms;
this.user = user;
}
public RtmAuth(Element elt) {
if (!elt.getNodeName().equals("auth")) { throw new IllegalArgumentException("Element " + elt.getNodeName() + " does not represent an Auth object."); }
this.token = text(child(elt, "token"));
this.perms = Enum.valueOf(Perms.class, text(child(elt, "perms")));
this.user = new RtmUser(child(elt, "user"));
}
public String getToken() {
return token;
}
public Perms getPerms() {
return perms;
}
public RtmUser getUser() {
return user;
}
}

@ -0,0 +1,119 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm.data;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
*
* @author Will Ross Jun 21, 2007
*/
public abstract class RtmData
{
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
public RtmData()
{
}
/**
* The method is not optimized at most, but circumvents a bug in Android runtime.
*/
public static Element child(Element elt, String nodeName)
{
NodeList childNodes = elt.getChildNodes();
for (int index = 0; index < childNodes.getLength(); index++)
{
Node child = childNodes.item(index);
if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(nodeName))
{
return (Element) child;
}
}
return null;
}
/**
* The method is not optimized at most, but circumvents a bug in Android runtime.
*/
public static List<Element> children(Element elt, String nodeName)
{
List<Element> result = new ArrayList<Element>();
NodeList childNodes = elt.getChildNodes();
for (int index = 0; index < childNodes.getLength(); index++)
{
Node child = childNodes.item(index);
if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(nodeName))
{
result.add((Element) child);
}
}
return result;
}
protected String text(Element elt)
{
StringBuilder result = new StringBuilder();
Node child = elt.getFirstChild();
while (child != null)
{
switch (child.getNodeType())
{
case Node.TEXT_NODE:
case Node.CDATA_SECTION_NODE:
result.append(child.getNodeValue());
break;
default:
break;
}
child = child.getNextSibling();
}
return result.toString();
}
public static Date parseDate(String s)
{
try
{
Date d = DATE_FORMAT.parse(s);
return new Date(d.getTime() + TimeZone.getDefault().getOffset(d.getTime()));
}
catch (ParseException e)
{
throw new RuntimeException(e);
}
}
public static String formatDate(Date d)
{
return DATE_FORMAT.format(new Date(d.getTime() - TimeZone.getDefault().getOffset(d.getTime()))) + "Z";
}
}

@ -0,0 +1,44 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm.data;
import org.w3c.dom.Element;
/**
*
* @author Will Ross Jun 21, 2007
*/
public class RtmFrob extends RtmData {
private final String value;
public RtmFrob(String value) {
this.value = value;
}
public RtmFrob(Element elt) {
this.value = text(elt);
}
public String getValue() {
return value;
}
}

@ -0,0 +1,47 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm.data;
import org.w3c.dom.Element;
public class RtmList extends RtmData {
private final String id;
private final String name;
public RtmList(String id, String name) {
this.id = id;
this.name = name;
}
public RtmList(Element elt) {
id = elt.getAttribute("id");
name = elt.getAttribute("name");
}
public String getId() {
return id;
}
public String getName() {
return name;
}
}

@ -0,0 +1,51 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm.data;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.w3c.dom.Element;
public class RtmLists extends RtmData {
private final Map<String, RtmList> lists;
public RtmLists() {
this.lists = new HashMap<String, RtmList>();
}
public RtmLists(Element elt) {
this.lists = new HashMap<String, RtmList>();
for (Element listElt : children(elt, "list")) {
RtmList list = new RtmList(listElt);
lists.put(list.getId(), list);
}
}
public RtmList getList(String id) {
return lists.get(id);
}
public Map<String, RtmList> getLists() {
return Collections.unmodifiableMap(lists);
}
}

@ -0,0 +1,59 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm.data;
import org.w3c.dom.Element;
/**
* Represents a location.
*
* @author Edouard Mercier
* @since 2008.05.22
*/
public class RtmLocation
extends RtmData
{
public final String id;
public final String name;
public final float longitude;
public final float latitude;
public final String address;
public final boolean viewable;
public int zoom;
public RtmLocation(Element element)
{
id = element.getAttribute("id");
name = element.getAttribute("name");
longitude = Float.parseFloat(element.getAttribute("longitude"));
latitude = Float.parseFloat(element.getAttribute("latitude"));
address = element.getAttribute("address");
zoom = Integer.parseInt(element.getAttribute("zoom"));
viewable = element.getAttribute("viewable").equals("1") ? true : false;
}
}

@ -0,0 +1,193 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm.data;
import java.util.Date;
import org.w3c.dom.Element;
import android.util.Log;
/**
*
* @author Will Ross Jun 21, 2007
*/
public class RtmTask
extends RtmData
{
private static final String TAG = "rtm-task";
private final String id;
private final Date due;
private final int hasDueTime;
private final Date added;
private final Date completed;
private final Date deleted;
private final Priority priority;
private final int postponed;
private final String estimate;
public enum Priority
{
High, Medium, Low, None
}
public static String convertPriority(Priority priority)
{
switch (priority)
{
case None:
return new String(new char[] { 'n' });
case Low:
return new String(new char[] { '3' });
case Medium:
return new String(new char[] { '2' });
case High:
return new String(new char[] { '1' });
default:
Log.e(TAG, "Unrecognized RTM task priority: '" + priority + "'");
return new String(new char[] { 'n' });
}
}
public RtmTask(String id, Date due, int hasDueTime, Date added, Date completed, Date deleted, Priority priority, int postponed, String estimate)
{
this.id = id;
this.due = due;
this.hasDueTime = hasDueTime;
this.added = added;
this.completed = completed;
this.deleted = deleted;
this.priority = priority;
this.postponed = postponed;
this.estimate = estimate;
}
public RtmTask(Element elt)
{
id = elt.getAttribute("id");
String dueStr = elt.getAttribute("due");
due = (dueStr == null || dueStr.length() == 0) ? null : parseDate(dueStr);
hasDueTime = Integer.parseInt(elt.getAttribute("has_due_time"));
String addedStr = elt.getAttribute("added");
added = (addedStr == null || addedStr.length() == 0) ? null : parseDate(addedStr);
String completedStr = elt.getAttribute("completed");
completed = (completedStr == null || completedStr.length() == 0) ? null : parseDate(completedStr);
String deletedStr = elt.getAttribute("deleted");
deleted = (deletedStr == null || deletedStr.length() == 0) ? null : parseDate(deletedStr);
String priorityStr = elt.getAttribute("priority");
if (priorityStr.length() > 0)
{
switch (priorityStr.charAt(0))
{
case 'N':
case 'n':
priority = Priority.None;
break;
case '3':
priority = Priority.Low;
break;
case '2':
priority = Priority.Medium;
break;
case '1':
priority = Priority.High;
break;
default:
System.err.println("Unrecognized RTM task priority: '" + priorityStr + "'");
priority = Priority.Medium;
}
}
else
{
priority = Priority.None;
}
if (elt.hasAttribute("postponed") == true && elt.getAttribute("postponed").length() > 0)
{
postponed = Integer.parseInt(elt.getAttribute("postponed"));
}
else
{
postponed = 0;
}
estimate = elt.getAttribute("estimate");
}
public String getId()
{
return id;
}
public Date getDue()
{
return due;
}
public int getHasDueTime()
{
return hasDueTime;
}
public Date getAdded()
{
return added;
}
public Date getCompleted()
{
return completed;
}
public Date getDeleted()
{
return deleted;
}
public Priority getPriority()
{
return priority;
}
public int getPostponed()
{
return postponed;
}
public String getEstimate()
{
return estimate;
}
@Override
public String toString()
{
return "Task<" + id + ">";
}
}

@ -0,0 +1,60 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm.data;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.w3c.dom.Element;
/**
*
* @author Will Ross Jun 22, 2007
*/
public class RtmTaskList extends RtmData {
private final String id;
private final List<RtmTaskSeries> series;
public RtmTaskList(String id) {
this.id = id;
this.series = new ArrayList<RtmTaskSeries>();
}
public RtmTaskList(Element elt) {
id = elt.getAttribute("id");
series = new ArrayList<RtmTaskSeries>();
for (Element seriesElt : children(elt, "taskseries")) {
series.add(new RtmTaskSeries(seriesElt));
}
if (id == null || id.length() == 0) { throw new RuntimeException("No id found in task list."); }
}
public String getId() {
return id;
}
public List<RtmTaskSeries> getSeries() {
return Collections.unmodifiableList(series);
}
}

@ -0,0 +1,85 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm.data;
import java.util.Date;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
/**
* Represents a single task note.
*
* @author Edouard Mercier
* @since 2008.04.22
*/
public class RtmTaskNote
extends RtmData
{
private String id;
private Date created;
private Date modified;
private String title;
private String text;
public RtmTaskNote(Element element)
{
id = element.getAttribute("id");
created = parseDate(element.getAttribute("created"));
modified = parseDate(element.getAttribute("modified"));
title = element.getAttribute("title");
if (element.getChildNodes().getLength() > 0)
{
Text innerText = (Text) element.getChildNodes().item(0);
text = innerText.getData();
}
}
public String getId()
{
return id;
}
public Date getCreated()
{
return created;
}
public Date getModified()
{
return modified;
}
public String getTitle()
{
return title;
}
public String getText()
{
return text;
}
}

@ -0,0 +1,53 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm.data;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Element;
/**
* Represents the notes of a task.
*
* @author Edouard Mercier
* @since 2008.04.22
*/
public class RtmTaskNotes
extends RtmData
{
private List<RtmTaskNote> notes;
public RtmTaskNotes(Element element)
{
notes = new ArrayList<RtmTaskNote>();
for (Element child : children(element, "note"))
{
notes.add(new RtmTaskNote(child));
}
}
public List<RtmTaskNote> getNotes()
{
return notes;
}
}

@ -0,0 +1,125 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm.data;
import java.util.Date;
import java.util.logging.Logger;
import org.w3c.dom.Element;
/**
*
* @author Will Ross Jun 22, 2007
*/
public class RtmTaskSeries extends RtmData {
private static final Logger log = Logger.getLogger("TaskSeries");
private final String id;
private final Date created;
private final Date modified;
private final String name;
private final String source;
private final RtmTask task;
private final RtmTaskNotes notes;
private final String locationId;
private final String url;
public RtmTaskSeries(String id, Date created, Date modified, String name, String source, RtmTask task) {
this.id = id;
this.created = created;
this.modified = modified;
this.name = name;
this.source = source;
this.task = task;
this.locationId = null;
notes = null;
url = null;
}
public RtmTaskSeries(Element elt) {
id = elt.getAttribute("id");
created = parseDate(elt.getAttribute("created"));
modified = parseDate(elt.getAttribute("modified"));
name = elt.getAttribute("name");
source = elt.getAttribute("source");
task = new RtmTask(child(elt, "task"));
if (children(elt, "task").size() > 1) {
log.severe("WARANING: Assumption incorrect: found a TaskSeries with more than one child Task.");
}
notes = new RtmTaskNotes(child(elt, "notes"));
locationId = elt.getAttribute("location_id");
url = elt.getAttribute("url");
}
public String getId() {
return id;
}
public Date getCreated() {
return created;
}
public Date getModified() {
return modified;
}
public String getName() {
return name;
}
public String getSource() {
return source;
}
public RtmTask getTask() {
return task;
}
public RtmTaskNotes getNotes()
{
return notes;
}
public String getLocationId()
{
return locationId;
}
@Override
public String toString()
{
return "TaskSeries<" + id + "," + name + ">";
}
public String getURL()
{
return url;
}
}

@ -0,0 +1,49 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm.data;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.w3c.dom.Element;
/**
*
* @author Will Ross Jun 21, 2007
*/
public class RtmTasks extends RtmData {
private final List<RtmTaskList> lists;
public RtmTasks() {
this.lists = new ArrayList<RtmTaskList>();
}
public RtmTasks(Element elt) {
this.lists = new ArrayList<RtmTaskList>();
for (Element listElt : children(elt, "list")) {
lists.add(new RtmTaskList(listElt));
}
}
public List<RtmTaskList> getLists() {
return Collections.unmodifiableList(lists);
}
}

@ -0,0 +1,39 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm.data;
import org.w3c.dom.Element;
public class RtmTimeline extends RtmData {
private final String id;
public RtmTimeline(String id) {
this.id = id;
}
public RtmTimeline(Element elt) {
id = text(elt);
}
public String getId() {
return id;
}
}

@ -0,0 +1,62 @@
/*
* Copyright 2007, MetaDimensional Technologies Inc.
*
*
* This file is part of the RememberTheMilk Java API.
*
* The RememberTheMilk Java API is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* The RememberTheMilk Java API is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mdt.rtm.data;
import org.w3c.dom.Element;
/**
*
* @author Will Ross Jun 21, 2007
*/
public class RtmUser extends RtmData {
private final String id;
private final String username;
private final String fullname;
public RtmUser(String id, String username, String fullname) {
this.id = id;
this.username = username;
this.fullname = fullname;
}
public RtmUser(Element elt) {
if (!elt.getNodeName().equals("user")) { throw new IllegalArgumentException("Element " + elt.getNodeName() + " does not represent a User object."); }
this.id = elt.getAttribute("id");
this.username = elt.getAttribute("username");
this.fullname = elt.getAttribute("fullname");
}
public String getId() {
return id;
}
public String getUsername() {
return username;
}
public String getFullname() {
return fullname;
}
}

@ -49,26 +49,34 @@ import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.SimpleCursorAdapter;
import android.widget.Spinner;
import android.widget.TabHost;
import android.widget.TabWidget;
import android.widget.TextView;
import android.widget.CompoundButton.OnCheckedChangeListener;
import com.timsu.astrid.R;
import com.timsu.astrid.data.alerts.AlertController;
import com.timsu.astrid.data.enums.Importance;
import com.timsu.astrid.data.enums.RepeatInterval;
import com.timsu.astrid.data.tag.TagController;
import com.timsu.astrid.data.tag.TagIdentifier;
import com.timsu.astrid.data.tag.TagModelForView;
import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.data.task.TaskModelForEdit;
import com.timsu.astrid.data.task.TaskModelForList;
import com.timsu.astrid.data.task.AbstractTaskModel.RepeatInfo;
import com.timsu.astrid.utilities.Constants;
import com.timsu.astrid.utilities.Notifications;
import com.timsu.astrid.utilities.Preferences;
import com.timsu.astrid.widget.DateControlSet;
import com.timsu.astrid.widget.DateWithNullControlSet;
import com.timsu.astrid.widget.NumberPicker;
import com.timsu.astrid.widget.NumberPickerDialog;
import com.timsu.astrid.widget.TimeDurationControlSet;
import com.timsu.astrid.widget.NumberPickerDialog.OnNumberPickedListener;
import com.timsu.astrid.widget.TimeDurationControlSet.TimeDurationType;
public class TaskEdit extends TaskModificationTabbedActivity<TaskModelForEdit> {
@ -99,11 +107,14 @@ public class TaskEdit extends TaskModificationTabbedActivity<TaskModelForEdit> {
private DateControlSet hiddenUntil;
private EditText notes;
private LinearLayout tagsContainer;
private NotificationFlagControlSet flags;
private NotifyFlagControlSet flags;
private LinearLayout alertsContainer;
private Button repeatValue;
private Spinner repeatInterval;
// other instance variables
private boolean shouldSaveState = true;
private boolean repeatHelpShown = false;
private TagController tagController;
private AlertController alertController;
private List<TagModelForView> tags;
@ -119,23 +130,29 @@ public class TaskEdit extends TaskModificationTabbedActivity<TaskModelForEdit> {
alertController.open();
TabHost tabHost = getTabHost();
tabHost.setPadding(0, 4, 0, 0);
Resources r = getResources();
LayoutInflater.from(this).inflate(R.layout.task_edit,
tabHost.getTabContentView(), true);
tabHost.addTab(tabHost.newTabSpec(TAB_BASIC)
.setIndicator("Basic",
.setIndicator("",
r.getDrawable(R.drawable.ic_dialog_info_c))
.setContent(R.id.tab_basic));
tabHost.addTab(tabHost.newTabSpec(TAB_DATES)
.setIndicator("Details",
.setIndicator("",
r.getDrawable(R.drawable.ic_dialog_time_c))
.setContent(R.id.tab_dates));
tabHost.addTab(tabHost.newTabSpec(TAB_ALERTS)
.setIndicator("Alerts",
.setIndicator("",
r.getDrawable(R.drawable.ic_dialog_alert_c))
.setContent(R.id.tab_notification));
TabWidget tabWidget = tabHost.getTabWidget();
for(int i = 0; i < tabWidget.getChildCount(); i++) {
RelativeLayout tab = (RelativeLayout)tabWidget.getChildAt(i);
tab.getLayoutParams().height = 44;
}
setUpUIComponents();
setUpListeners();
@ -210,6 +227,14 @@ public class TaskEdit extends TaskModificationTabbedActivity<TaskModelForEdit> {
addAlert(alert);
}
}
// repeats
RepeatInfo repeatInfo = model.getRepeat();
if(repeatInfo != null) {
setRepeatValue(repeatInfo.getValue());
repeatInterval.setSelection(repeatInfo.getInterval().ordinal());
} else
setRepeatValue(0);
}
private void save() {
@ -228,6 +253,7 @@ public class TaskEdit extends TaskModificationTabbedActivity<TaskModelForEdit> {
model.setNotificationFlags(flags.getValue());
model.setNotes(notes.getText().toString());
model.setNotificationIntervalSeconds(notification.getTimeDurationInSeconds());
model.setRepeat(getRepeatValue());
try {
// write out to database
@ -319,9 +345,11 @@ public class TaskEdit extends TaskModificationTabbedActivity<TaskModelForEdit> {
hiddenUntil = new DateWithNullControlSet(this, R.id.hiddenUntil_notnull,
R.id.hiddenUntil_date, R.id.hiddenUntil_time);
notes = (EditText)findViewById(R.id.notes);
flags = new NotificationFlagControlSet(R.id.flag_before,
flags = new NotifyFlagControlSet(R.id.flag_before,
R.id.flag_during, R.id.flag_after);
alertsContainer = (LinearLayout)findViewById(R.id.alert_container);
repeatInterval = (Spinner)findViewById(R.id.repeat_interval);
repeatValue = (Button)findViewById(R.id.repeat_value);
// individual ui component initialization
ImportanceAdapter importanceAdapter = new ImportanceAdapter(this,
@ -329,6 +357,11 @@ public class TaskEdit extends TaskModificationTabbedActivity<TaskModelForEdit> {
R.layout.importance_spinner_dropdown,
Importance.values());
importance.setAdapter(importanceAdapter);
ArrayAdapter<String> repeatAdapter = new ArrayAdapter<String>(
this, android.R.layout.simple_spinner_item,
RepeatInterval.getLabels(getResources()));
repeatAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
repeatInterval.setAdapter(repeatAdapter);
}
/** Set up button listeners */
@ -365,6 +398,29 @@ public class TaskEdit extends TaskModificationTabbedActivity<TaskModelForEdit> {
}
});
repeatValue.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
repeatValueClick();
}
});
}
/** Set up the repeat value button */
private void setRepeatValue(int value) {
if(value == 0)
repeatValue.setText(R.string.repeat_value_unset);
else
repeatValue.setText(Integer.toString(value));
repeatValue.setTag(value);
}
private RepeatInfo getRepeatValue() {
if(repeatValue.getTag().equals(0))
return null;
return new RepeatInfo(RepeatInterval.values()
[repeatInterval.getSelectedItemPosition()],
(Integer)repeatValue.getTag());
}
/** Adds an alert to the alert field */
@ -488,6 +544,57 @@ public class TaskEdit extends TaskModificationTabbedActivity<TaskModelForEdit> {
.show();
}
private void repeatValueClick() {
final int tagValue = (Integer)repeatValue.getTag();
if(tagValue > 0)
repeatHelpShown = true;
final Runnable openDialogRunnable = new Runnable() {
@Override
public void run() {
repeatHelpShown = true;
int dialogValue = tagValue;
if(dialogValue == 0)
dialogValue = 1;
new NumberPickerDialog(TaskEdit.this, new OnNumberPickedListener() {
@Override
public void onNumberPicked(NumberPicker view, int number) {
setRepeatValue(number);
}
}, getResources().getString(R.string.repeat_picker_title),
dialogValue, 1, 0, 31).show();
}
};
if(repeatHelpShown || !Preferences.shouldShowRepeatHelp(this)) {
openDialogRunnable.run();
return;
}
new AlertDialog.Builder(this)
.setTitle(R.string.repeat_help_dialog_title)
.setMessage(R.string.repeat_help_dialog)
.setIcon(android.R.drawable.ic_dialog_info)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
openDialogRunnable.run();
}
})
.setNeutralButton(R.string.repeat_help_hide,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Preferences.setShowRepeatHelp(TaskEdit.this, false);
openDialogRunnable.run();
}
})
.show();
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
switch(item.getItemId()) {
@ -614,10 +721,10 @@ public class TaskEdit extends TaskModificationTabbedActivity<TaskModelForEdit> {
}
/** Control set dealing with notification flags */
public class NotificationFlagControlSet {
public class NotifyFlagControlSet {
private CheckBox before, during, after;
public NotificationFlagControlSet(int beforeId, int duringId,
public NotifyFlagControlSet(int beforeId, int duringId,
int afterId) {
before = (CheckBox)findViewById(beforeId);
during = (CheckBox)findViewById(duringId);

@ -31,6 +31,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.Menu;
@ -76,7 +77,10 @@ public class TaskList extends Activity {
private static final int INSERT_ID = Menu.FIRST;
private static final int FILTERS_ID = Menu.FIRST + 1;
private static final int TAGS_ID = Menu.FIRST + 2;
private static final int SETTINGS_ID = Menu.FIRST + 3;
private static final int MORE_ID = Menu.FIRST + 3;
private static final int OPTIONS_SETTINGS_ID = Menu.FIRST + 10;
private static final int OPTIONS_HELP_ID = Menu.FIRST + 11;
private static final int CONTEXT_FILTER_HIDDEN = Menu.FIRST + 20;
private static final int CONTEXT_FILTER_DONE = Menu.FIRST + 21;
@ -87,6 +91,7 @@ public class TaskList extends Activity {
private TagController tagController = null;
private ListView listView;
private Button addButton;
private View layout;
// other instance variables
private Map<TagIdentifier, TagModelForView> tagMap;
@ -126,7 +131,20 @@ public class TaskList extends Activity {
}
});
layout = findViewById(R.id.tasklist_layout);
layout.setOnCreateContextMenuListener(
new OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
if(menu.hasVisibleItems())
return;
onCreateMoreOptionsMenu(menu);
}
});
fillData();
// TODO Synchronizer.authenticate(this);
}
@Override
@ -150,11 +168,27 @@ public class TaskList extends Activity {
item.setIcon(android.R.drawable.ic_menu_myplaces);
item.setAlphabeticShortcut('t');
item = menu.add(Menu.NONE, SETTINGS_ID, Menu.NONE,
item = menu.add(Menu.NONE, MORE_ID, Menu.NONE,
R.string.taskList_menu_more);
item.setIcon(android.R.drawable.ic_menu_more);
item.setAlphabeticShortcut('m');
return true;
}
public boolean onCreateMoreOptionsMenu(Menu menu) {
MenuItem item;
item = menu.add(Menu.NONE, OPTIONS_SETTINGS_ID, Menu.NONE,
R.string.taskList_menu_settings);
item.setIcon(android.R.drawable.ic_menu_preferences);
item.setAlphabeticShortcut('p');
item = menu.add(Menu.NONE, OPTIONS_HELP_ID, Menu.NONE,
R.string.taskList_menu_help);
item.setIcon(android.R.drawable.ic_menu_help);
item.setAlphabeticShortcut('h');
return true;
}
@ -352,6 +386,16 @@ public class TaskList extends Activity {
* ======================================================= event handlers
* ====================================================================== */
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
/** Handle case where authentication just happened */
if(resultCode == Constants.RESULT_SYNCHRONIZE) {
// TODO Synchronizer.performSync(this, true);
}
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
@ -410,9 +454,18 @@ public class TaskList extends Activity {
finish();
}
return true;
case SETTINGS_ID:
case MORE_ID:
layout.showContextMenu();
return true;
case OPTIONS_SETTINGS_ID:
startActivity(new Intent(this, EditPreferences.class));
return true;
case OPTIONS_HELP_ID:
Intent browserIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse(Constants.HELP_URL));
startActivity(browserIntent);
return true;
case TaskListAdapter.CONTEXT_EDIT_ID:
task = taskArray.get(item.getGroupId());

@ -184,7 +184,6 @@ public class TaskListAdapter extends ArrayAdapter<TaskModelForList> {
private void addListeners(final int position, final View view) {
final CheckBox progress = ((CheckBox)view.findViewById(R.id.cb1));
final TextView name = ((TextView)view.findViewById(R.id.text1));
// clicking the check box
progress.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@ -202,7 +201,7 @@ public class TaskListAdapter extends ArrayAdapter<TaskModelForList> {
if(newProgressPercentage != task.getProgressPercentage()) {
setTaskProgress(task, view, newProgressPercentage);
setTaskAppearance(task, name, progress);
setupView(view, task);
}
}
});

@ -81,6 +81,6 @@ public class TaskViewNotifier extends TaskView {
TaskList.shouldCloseInstance = true;
finish();
}
}, r.getString(R.string.notify_snooze_title), 15, 15, 0, 120).show();
}, r.getString(R.string.notify_snooze_title), 10, 10, 0, 120).show();
}
}

@ -0,0 +1,71 @@
/*
* ASTRID: Android's Simple Task Recording Dashboard
*
* Copyright (c) 2009 Tim Su
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.timsu.astrid.data.enums;
import java.util.Date;
import android.content.res.Resources;
import com.timsu.astrid.R;
public enum RepeatInterval {
DAYS(R.string.repeat_days) {
@Override
public void offsetDateBy(Date input, int number) {
input.setDate(input.getDate() + number);
}
},
WEEKS(R.string.repeat_weeks) {
@Override
public void offsetDateBy(Date input, int number) {
input.setDate(input.getDate() + 7 * number);
}
},
MONTHS(R.string.repeat_months) {
@Override
public void offsetDateBy(Date input, int number) {
input.setMonth(input.getMonth() + number);
}
},
;
int label;
private RepeatInterval(int label) {
this.label = label;
}
public int getLabelResource() {
return label;
}
abstract public void offsetDateBy(Date input, int number);
public static String[] getLabels(Resources r) {
int intervalCount = RepeatInterval.values().length;
String[] result = new String[intervalCount];
for(int i = 0; i < intervalCount; i++)
result[i] = r.getString(RepeatInterval.values()[i].getLabelResource());
return result;
}
}

@ -32,6 +32,7 @@ import com.timsu.astrid.R;
import com.timsu.astrid.data.AbstractController;
import com.timsu.astrid.data.AbstractModel;
import com.timsu.astrid.data.enums.Importance;
import com.timsu.astrid.data.enums.RepeatInterval;
/** Abstract model of a task. Subclasses implement the getters and setters
@ -43,7 +44,7 @@ import com.timsu.astrid.data.enums.Importance;
public abstract class AbstractTaskModel extends AbstractModel {
/** Version number of this model */
static final int VERSION = 2;
static final int VERSION = 3;
public static final int COMPLETE_PERCENTAGE = 100;
@ -62,16 +63,19 @@ public abstract class AbstractTaskModel extends AbstractModel {
static final String NOTIFICATIONS = "notifications";
static final String NOTIFICATION_FLAGS = "notificationFlags";
static final String LAST_NOTIFIED = "lastNotified";
// reserved fields
static final String BLOCKING_ON = "blockingOn";
// end reserved fields
static final String REPEAT = "repeat";
static final String CREATION_DATE = "creationDate";
static final String COMPLETION_DATE = "completionDate";
// reserved fields ---
static final String BLOCKING_ON = "blockingOn";
// notification flags
public static final int NOTIFY_BEFORE_DEADLINE = 1;
public static final int NOTIFY_AT_DEADLINE = 2;
public static final int NOTIFY_AFTER_DEADLINE = 4;
public static final int NOTIFY_BEFORE_DEADLINE = 1 << 0;
public static final int NOTIFY_AT_DEADLINE = 1 << 1;
public static final int NOTIFY_AFTER_DEADLINE = 1 << 2;
/** Number of bits to shift repeat value by */
public static final int REPEAT_VALUE_OFFSET = 3;
/** Default values container */
private static final ContentValues defaultValues = new ContentValues();
@ -91,14 +95,10 @@ public abstract class AbstractTaskModel extends AbstractModel {
defaultValues.put(NOTIFICATIONS, 0);
defaultValues.put(NOTIFICATION_FLAGS, NOTIFY_AT_DEADLINE);
defaultValues.put(LAST_NOTIFIED, (Long)null);
defaultValues.put(REPEAT, 0);
defaultValues.put(COMPLETION_DATE, (Long)null);
}
@Override
public ContentValues getDefaultValues() {
return defaultValues;
}
// --- database helper
/** Database Helper manages creating new tables and updating old ones */
@ -129,6 +129,7 @@ public abstract class AbstractTaskModel extends AbstractModel {
append(NOTIFICATIONS).append(" integer,").
append(NOTIFICATION_FLAGS).append(" integer,").
append(LAST_NOTIFIED).append(" integer,").
append(REPEAT).append(" integer,").
append(CREATION_DATE).append(" integer,").
append(COMPLETION_DATE).append(" integer").
append(");").toString();
@ -136,14 +137,16 @@ public abstract class AbstractTaskModel extends AbstractModel {
}
@Override
@SuppressWarnings("fallthrough")
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(getClass().getSimpleName(), "Upgrading database from version " +
oldVersion + " to " + newVersion + ".");
String sql;
// note: we execute sql statements in their own try block to be more
// graceful if an upgrade dies halfway or something
switch(oldVersion) {
case 1:
// for some reason, these don't always go well
sql = new StringBuilder().append("ALTER TABLE ").
append(tableName).append(" ADD COLUMN ").
append(LAST_NOTIFIED).append(" integer").toString();
@ -161,6 +164,16 @@ public abstract class AbstractTaskModel extends AbstractModel {
Log.e("astrid", "Error updating table!", e);
}
case 2:
sql = new StringBuilder().append("ALTER TABLE ").
append(tableName).append(" ADD COLUMN ").
append(REPEAT).append(" integer").toString();
try {
db.execSQL(sql);
} catch (Exception e) {
Log.e("astrid", "Error updating table!", e);
}
break;
default:
// we don't know how to handle it... do the unfortunate thing
@ -200,7 +213,6 @@ public abstract class AbstractTaskModel extends AbstractModel {
setElapsedSeconds((int) (getElapsedSeconds() + secondsElapsed));
}
protected void prefetchData(String[] fields) {
for(String field : fields) {
if(field.equals(NAME))
@ -235,7 +247,36 @@ public abstract class AbstractTaskModel extends AbstractModel {
getNotificationFlags();
else if(field.equals(LAST_NOTIFIED))
getLastNotificationDate();
else if(field.equals(REPEAT))
getRepeat();
}
}
// --- helper classes
public static class RepeatInfo {
private RepeatInterval interval;
private int value;
public RepeatInfo(RepeatInterval repeatInterval, int value) {
this.interval = repeatInterval;
this.value = value;
}
public Date shiftDate(Date input) {
Date newDate = (Date)input.clone();
interval.offsetDateBy(newDate, value);
return newDate;
}
public RepeatInterval getInterval() {
return interval;
}
public int getValue() {
return value;
}
}
// --- task identifier
@ -250,7 +291,7 @@ public abstract class AbstractTaskModel extends AbstractModel {
this.identifier = identifier;
}
// --- constructor pass-through
// --- constructors and abstract methods
AbstractTaskModel() {
super();
@ -271,6 +312,11 @@ public abstract class AbstractTaskModel extends AbstractModel {
setTaskIdentifier(identifier);
}
@Override
public ContentValues getDefaultValues() {
return defaultValues;
}
// --- getters and setters: expose them as you see fit
protected String getName() {
@ -349,6 +395,17 @@ public abstract class AbstractTaskModel extends AbstractModel {
return retrieveDate(LAST_NOTIFIED);
}
protected RepeatInfo getRepeat() {
int repeat = retrieveInteger(REPEAT);
if(repeat == 0)
return null;
int value = repeat >> REPEAT_VALUE_OFFSET;
RepeatInterval interval = RepeatInterval.values()
[repeat - (value << REPEAT_VALUE_OFFSET)];
return new RepeatInfo(interval, value);
}
// --- setters
protected void setName(String name) {
@ -422,6 +479,16 @@ public abstract class AbstractTaskModel extends AbstractModel {
putDate(LAST_NOTIFIED, date);
}
protected void setRepeat(RepeatInfo repeatInfo) {
int repeat;
if(repeatInfo == null)
repeat = 0;
else
repeat = (repeatInfo.value << REPEAT_VALUE_OFFSET) +
repeatInfo.interval.ordinal();
putIfChangedFromDatabase(REPEAT, repeat);
}
// --- utility methods
protected void putDate(String fieldName, Date date) {

@ -32,6 +32,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.timsu.astrid.data.AbstractController;
import com.timsu.astrid.data.task.AbstractTaskModel.RepeatInfo;
import com.timsu.astrid.data.task.AbstractTaskModel.TaskModelDatabaseHelper;
import com.timsu.astrid.utilities.Notifications;
@ -163,11 +164,11 @@ public class TaskController extends AbstractController {
if(values.size() == 0) // nothing changed
return true;
// set completion date
// if this task is completed, perform some handling
if(values.containsKey(AbstractTaskModel.PROGRESS_PERCENTAGE) &&
values.getAsInteger(AbstractTaskModel.PROGRESS_PERCENTAGE)
== AbstractTaskModel.COMPLETE_PERCENTAGE) {
values.put(AbstractTaskModel.COMPLETION_DATE, System.currentTimeMillis());
onTaskSetCompleted(task, values);
}
saveSucessful = database.update(TASK_TABLE_NAME, values,
@ -177,6 +178,24 @@ public class TaskController extends AbstractController {
return saveSucessful;
}
/** Called when this task is set to completed.
*
* @param task task to process
* @param values mutable map of values to save
*/
private void onTaskSetCompleted(AbstractTaskModel task, ContentValues values) {
values.put(AbstractTaskModel.COMPLETION_DATE, System.currentTimeMillis());
// handle repeat
Cursor cursor = fetchTaskCursor(task.getTaskIdentifier(),
TaskModelForRepeat.FIELD_LIST);
TaskModelForRepeat repeatModel = new TaskModelForRepeat(cursor, values);
RepeatInfo repeatInfo = repeatModel.getRepeat();
if(repeatInfo != null)
repeatModel.repeatTaskBy(repeatInfo);
cursor.close();
}
/** Set last notification date */
public boolean setLastNotificationTime(TaskIdentifier taskId, Date date) {
ContentValues values = new ContentValues();
@ -198,71 +217,49 @@ public class TaskController extends AbstractController {
/** Returns a TaskModelForEdit corresponding to the given TaskIdentifier */
public TaskModelForEdit fetchTaskForEdit(Activity activity, TaskIdentifier
taskId) throws SQLException {
long id = taskId.getId();
Cursor cursor = database.query(true, TASK_TABLE_NAME,
TaskModelForEdit.FIELD_LIST,
KEY_ROWID + "=" + id, null, null, null, null, null);
Cursor cursor = fetchTaskCursor(taskId, TaskModelForEdit.FIELD_LIST);
activity.startManagingCursor(cursor);
if (cursor != null) {
cursor.moveToFirst();
TaskModelForEdit model = new TaskModelForEdit(taskId, cursor);
return model;
}
throw new SQLException("Returned empty set!");
TaskModelForEdit model = new TaskModelForEdit(taskId, cursor);
return model;
}
/** Returns a TaskModelForView corresponding to the given TaskIdentifier */
public TaskModelForView fetchTaskForView(Activity activity,
TaskIdentifier taskId) throws SQLException {
long id = taskId.getId();
Cursor cursor = database.query(true, TASK_TABLE_NAME,
TaskModelForView.FIELD_LIST,
KEY_ROWID + "=" + id, null, null, null, null, null);
Cursor cursor = fetchTaskCursor(taskId, TaskModelForView.FIELD_LIST);
activity.startManagingCursor(cursor);
if (cursor != null) {
cursor.moveToFirst();
TaskModelForView model = new TaskModelForView(taskId, cursor);
return model;
}
throw new SQLException("Returned empty set!");
TaskModelForView model = new TaskModelForView(taskId, cursor);
return model;
}
/** Returns a TaskModelForView corresponding to the given TaskIdentifier */
public TaskModelForList fetchTaskForList(TaskIdentifier taskId) throws SQLException {
long id = taskId.getId();
Cursor cursor = database.query(true, TASK_TABLE_NAME,
TaskModelForList.FIELD_LIST,
KEY_ROWID + "=" + id, null, null, null, null, null);
if (cursor != null) {
cursor.moveToFirst();
TaskModelForList model = new TaskModelForList(cursor);
cursor.close();
return model;
}
throw new SQLException("Returned empty set!");
Cursor cursor = fetchTaskCursor(taskId, TaskModelForList.FIELD_LIST);
TaskModelForList model = new TaskModelForList(cursor);
cursor.close();
return model;
}
/** Returns a TaskModelForView corresponding to the given TaskIdentifier */
public TaskModelForNotify fetchTaskForNotify(TaskIdentifier taskId) throws SQLException {
Cursor cursor = fetchTaskCursor(taskId, TaskModelForNotify.FIELD_LIST);
TaskModelForNotify model = new TaskModelForNotify(cursor);
cursor.close();
return model;
}
/** Returns null if unsuccessful, otherwise moves cursor to the task.
* Don't forget to close the cursor when you're done. */
private Cursor fetchTaskCursor(TaskIdentifier taskId, String[] fieldList) {
long id = taskId.getId();
Cursor cursor = database.query(true, TASK_TABLE_NAME,
TaskModelForNotify.FIELD_LIST,
Cursor cursor = database.query(true, TASK_TABLE_NAME, fieldList,
KEY_ROWID + "=" + id, null, null, null, null, null);
if (cursor != null) {
cursor.moveToFirst();
TaskModelForNotify model = new TaskModelForNotify(cursor);
cursor.close();
return model;
}
if (cursor == null)
throw new SQLException("Returned empty set!");
throw new SQLException("Returned empty set!");
cursor.moveToFirst();
return cursor;
}
// --- boilerplate

@ -45,6 +45,7 @@ public class TaskModelForEdit extends AbstractTaskModel implements Notifiable {
LAST_NOTIFIED,
PROGRESS_PERCENTAGE,
NOTES,
REPEAT,
};
// --- constructors
@ -130,6 +131,11 @@ public class TaskModelForEdit extends AbstractTaskModel implements Notifiable {
return super.getLastNotificationDate();
}
@Override
public RepeatInfo getRepeat() {
return super.getRepeat();
}
@Override
public void setDefiniteDueDate(Date definiteDueDate) {
super.setDefiniteDueDate(definiteDueDate);
@ -180,4 +186,8 @@ public class TaskModelForEdit extends AbstractTaskModel implements Notifiable {
super.setNotificationFlags(flags);
}
@Override
public void setRepeat(RepeatInfo taskRepeat) {
super.setRepeat(taskRepeat);
}
}

@ -0,0 +1,83 @@
/*
* ASTRID: Android's Simple Task Recording Dashboard
*
* Copyright (c) 2009 Tim Su
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.timsu.astrid.data.task;
import java.util.Date;
import android.content.ContentValues;
import android.database.Cursor;
import com.timsu.astrid.data.AbstractController;
/** Fields that you would want to edit pertaining to repeats */
public class TaskModelForRepeat extends AbstractTaskModel {
static String[] FIELD_LIST = new String[] {
AbstractController.KEY_ROWID,
REPEAT,
DEFINITE_DUE_DATE,
PREFERRED_DUE_DATE,
HIDDEN_UNTIL,
PROGRESS_PERCENTAGE,
};
public void repeatTaskBy(RepeatInfo repeatInfo) {
if(getDefiniteDueDate() != null)
setDefiniteDueDate(repeatInfo.shiftDate(getDefiniteDueDate()));
if(getHiddenUntil() != null)
setHiddenUntil(repeatInfo.shiftDate(getHiddenUntil()));
if(getPreferredDueDate() != null)
setPreferredDueDate(repeatInfo.shiftDate(getPreferredDueDate()));
setProgressPercentage(0);
// TODO shift fixed alerts?
}
// --- constructors
public TaskModelForRepeat(Cursor cursor, ContentValues setValues) {
super(cursor);
this.setValues = setValues;
}
// --- getters and setters
@Override
public RepeatInfo getRepeat() {
return super.getRepeat();
}
@Override
public void setDefiniteDueDate(Date definiteDueDate) {
super.setDefiniteDueDate(definiteDueDate);
}
@Override
public void setPreferredDueDate(Date preferredDueDate) {
super.setPreferredDueDate(preferredDueDate);
}
@Override
public void setHiddenUntil(Date hiddenUntil) {
super.setHiddenUntil(hiddenUntil);
}
}

@ -0,0 +1,71 @@
package com.timsu.astrid.sync;
import java.util.Map.Entry;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import com.mdt.rtm.ApplicationInfo;
import com.mdt.rtm.ServiceImpl;
import com.mdt.rtm.data.RtmList;
import com.mdt.rtm.data.RtmLists;
import com.mdt.rtm.data.RtmAuth.Perms;
import com.timsu.astrid.utilities.Constants;
public class Synchronizer {
private static ServiceImpl rtmService = null;
public static void authenticate(Activity activity) {
try {
String apiKey = "127d19adab1a7b6922d8dfda3ef09645";
String sharedSecret = "503816890a685753";
String appName = null;
String authToken = null;
// check if our auth token works
if(authToken != null) {
rtmService = new ServiceImpl(new ApplicationInfo(
apiKey, sharedSecret, appName, authToken));
if(!rtmService.isServiceAuthorized()) // re-do login
authToken = null;
}
if(authToken == null) {
rtmService = new ServiceImpl(new ApplicationInfo(
apiKey, sharedSecret, appName));
String url = rtmService.beginAuthorization(Perms.delete);
Intent browserIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse(url));
activity.startActivityForResult(browserIntent,
Constants.RESULT_SYNCHRONIZE);
} else {
performSync(activity, false);
}
} catch (Exception e) {
Log.e("astrid", "error parsing", e); // TODO dialog box
}
}
public static void performSync(Activity activity, boolean justLoggedIn) {
try {
// store token
if(justLoggedIn) {
String token = rtmService.completeAuthorization();
Log.w("astrid", "LOOK A TOKEN " + token);
}
System.out.println("isAuthorized: " + rtmService.isServiceAuthorized());
RtmLists lists = rtmService.lists_getList();
for(Entry<String, RtmList> list : lists.getLists().entrySet()) {
Log.i("astrid", "look, " + list.getKey());
}
} catch (Exception e) {
Log.e("astrid", "error parsing", e); // TODO dialog box
}
}
}

@ -5,8 +5,15 @@ import android.app.Activity;
/** Astrid constants */
public class Constants {
// application constants
public static final String HELP_URL = "http://code.google.com/p/android-astrid/wiki/UserGuide";
// result codes
/** Return to the task list view */
public static final int RESULT_GO_HOME = Activity.RESULT_FIRST_USER;
/** Callback from synchronization */
public static final int RESULT_SYNCHRONIZE = Activity.RESULT_FIRST_USER + 1;
}

@ -11,7 +11,11 @@ import com.timsu.astrid.R;
public class Preferences {
private static final String CURRENT_VERSION = "cv";
// pref keys
private static final String P_CURRENT_VERSION = "cv";
private static final String P_SHOW_REPEAT_HELP = "repeathelp";
// default values
private static final boolean DEFAULT_PERSISTENCE_MODE = true;
private static SharedPreferences getPrefs(Context context) {
@ -32,13 +36,26 @@ public class Preferences {
editor.commit();
}
/** CurrentVersion: the currently installed version of Astrid */
public static int getCurrentVersion(Context context) {
return getPrefs(context).getInt(CURRENT_VERSION, 0);
return getPrefs(context).getInt(P_CURRENT_VERSION, 0);
}
/** CurrentVersion: the currently installed version of Astrid */
public static void setCurrentVersion(Context context, int version) {
Editor editor = getPrefs(context).edit();
editor.putInt(CURRENT_VERSION, version);
editor.putInt(P_CURRENT_VERSION, version);
editor.commit();
}
/** ShowRepeatHelp: whether help dialog should be shown about repeats */
public static boolean shouldShowRepeatHelp(Context context) {
return getPrefs(context).getBoolean(P_SHOW_REPEAT_HELP, true);
}
public static void setShowRepeatHelp(Context context, boolean setting) {
Editor editor = getPrefs(context).edit();
editor.putBoolean(P_SHOW_REPEAT_HELP, setting);
editor.commit();
}

Loading…
Cancel
Save