diff --git a/.classpath b/.classpath
index 46b125664..dfe09c8f7 100644
--- a/.classpath
+++ b/.classpath
@@ -1,8 +1,8 @@
-
+
-
+
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 95d75fb99..f77139fbf 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,10 +1,11 @@
+ android:versionCode="37"
+ android:versionName="1.10.1">
+
diff --git a/lib/commons-codec-1.3.jar b/lib/commons-codec-1.3.jar
new file mode 100644
index 000000000..957b6752a
Binary files /dev/null and b/lib/commons-codec-1.3.jar differ
diff --git a/res/layout/task_edit.xml b/res/layout/task_edit.xml
index 380a20522..35c3e3701 100644
--- a/res/layout/task_edit.xml
+++ b/res/layout/task_edit.xml
@@ -32,13 +32,13 @@
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
-
+
-
+
@@ -80,10 +81,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 27edc7a49..bc1106916 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -32,6 +32,11 @@
It would be nice to do thisWho cares!
+
+ Day(s)
+ Week(s)
+ Month(s)
+
1 Task
@@ -81,8 +86,10 @@
AddTags
- SettingsFilters
+ More
+ Settings
+ HelpEdit TaskDelete Task
@@ -105,19 +112,21 @@
Alerts
- What
+ SummaryTask DescriptionHow Important is it?Tags:
- Notes
- Enter Task NotesHow Long Will it Take?Time Already Spent on TaskAbsolute DeadlineGoal DeadlineHide Until This Date
+ Repeat Every
+ No Repeat SetHide Until This Task is Done
+ Notes
+ Enter Task NotesPeriodic RemindersEvery
@@ -131,6 +140,15 @@
Time (hours : minutes)Remind Me Every
+ Repeat Every (0 to disable)
+ Help: Astrid Repeats
+
+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
+
+ Don't Show Help AnymoreSave
@@ -212,7 +230,10 @@
Quiet Hours EndEnding hour when Astrid should be quiet (e.g. 08)Persistence Mode
- If checked, Astrid will try harder to get your attention.
+
+If checked, Astrid will try harder to get your attention:
+ - Must view reminders before hiding them
+ Notification RingtoneChoose how Astrid alerts you!
diff --git a/src/com/mdt/rtm/ApplicationInfo.java b/src/com/mdt/rtm/ApplicationInfo.java
new file mode 100644
index 000000000..f12517630
--- /dev/null
+++ b/src/com/mdt/rtm/ApplicationInfo.java
@@ -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 .
+ */
+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;
+ }
+
+}
diff --git a/src/com/mdt/rtm/Invoker.java b/src/com/mdt/rtm/Invoker.java
new file mode 100644
index 000000000..4508fad2e
--- /dev/null
+++ b/src/com/mdt/rtm/Invoker.java
@@ -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 .
+ */
+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 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();
+ }
+
+}
diff --git a/src/com/mdt/rtm/Param.java b/src/com/mdt/rtm/Param.java
new file mode 100644
index 000000000..2a3e221bd
--- /dev/null
+++ b/src/com/mdt/rtm/Param.java
@@ -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 .
+ */
+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
+{
+
+ 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());
+ }
+}
diff --git a/src/com/mdt/rtm/Prefs.java b/src/com/mdt/rtm/Prefs.java
new file mode 100644
index 000000000..9bfce0184
--- /dev/null
+++ b/src/com/mdt/rtm/Prefs.java
@@ -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 .
+ */
+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);
+ }
+}
diff --git a/src/com/mdt/rtm/Service.java b/src/com/mdt/rtm/Service.java
new file mode 100644
index 000000000..00964e9ac
--- /dev/null
+++ b/src/com/mdt/rtm/Service.java
@@ -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 .
+ */
+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, isServiceAuthorized() 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 locations_getList()
+ throws ServiceException;
+
+}
diff --git a/src/com/mdt/rtm/ServiceException.java b/src/com/mdt/rtm/ServiceException.java
new file mode 100644
index 000000000..ff25555bd
--- /dev/null
+++ b/src/com/mdt/rtm/ServiceException.java
@@ -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 .
+ */
+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;
+ }
+}
diff --git a/src/com/mdt/rtm/ServiceImpl.java b/src/com/mdt/rtm/ServiceImpl.java
new file mode 100644
index 000000000..b8cdd06c3
--- /dev/null
+++ b/src/com/mdt/rtm/ServiceImpl.java
@@ -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 .
+ */
+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 null, 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 null, no authentication will be performed and the HTTP is
+ * considered working anonymously, and the last proxyPassword 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 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 params = new HashSet();
+ 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 params = new HashSet();
+ 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 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 locations = new ArrayList();
+ for (Element child : RtmData.children(result, "location"))
+ {
+ locations.add(new RtmLocation(child));
+ }
+ return locations;
+ }
+
+}
diff --git a/src/com/mdt/rtm/ServiceInternalException.java b/src/com/mdt/rtm/ServiceInternalException.java
new file mode 100644
index 000000000..f7e80f8d2
--- /dev/null
+++ b/src/com/mdt/rtm/ServiceInternalException.java
@@ -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 .
+ */
+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;
+ }
+
+}
diff --git a/src/com/mdt/rtm/data/RtmAuth.java b/src/com/mdt/rtm/data/RtmAuth.java
new file mode 100644
index 000000000..c16a8e441
--- /dev/null
+++ b/src/com/mdt/rtm/data/RtmAuth.java
@@ -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 .
+ */
+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;
+ }
+}
diff --git a/src/com/mdt/rtm/data/RtmData.java b/src/com/mdt/rtm/data/RtmData.java
new file mode 100644
index 000000000..bd455276f
--- /dev/null
+++ b/src/com/mdt/rtm/data/RtmData.java
@@ -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 .
+ */
+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 children(Element elt, String nodeName)
+ {
+ List result = new ArrayList();
+ 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";
+ }
+}
diff --git a/src/com/mdt/rtm/data/RtmFrob.java b/src/com/mdt/rtm/data/RtmFrob.java
new file mode 100644
index 000000000..dee923f13
--- /dev/null
+++ b/src/com/mdt/rtm/data/RtmFrob.java
@@ -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 .
+ */
+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;
+ }
+
+}
diff --git a/src/com/mdt/rtm/data/RtmList.java b/src/com/mdt/rtm/data/RtmList.java
new file mode 100644
index 000000000..34e3ff047
--- /dev/null
+++ b/src/com/mdt/rtm/data/RtmList.java
@@ -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 .
+ */
+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;
+ }
+}
diff --git a/src/com/mdt/rtm/data/RtmLists.java b/src/com/mdt/rtm/data/RtmLists.java
new file mode 100644
index 000000000..e14e9e1f9
--- /dev/null
+++ b/src/com/mdt/rtm/data/RtmLists.java
@@ -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 .
+ */
+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 lists;
+
+ public RtmLists() {
+ this.lists = new HashMap();
+ }
+
+ public RtmLists(Element elt) {
+ this.lists = new HashMap();
+ 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 getLists() {
+ return Collections.unmodifiableMap(lists);
+ }
+}
diff --git a/src/com/mdt/rtm/data/RtmLocation.java b/src/com/mdt/rtm/data/RtmLocation.java
new file mode 100644
index 000000000..81327af52
--- /dev/null
+++ b/src/com/mdt/rtm/data/RtmLocation.java
@@ -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 .
+ */
+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;
+ }
+
+}
diff --git a/src/com/mdt/rtm/data/RtmTask.java b/src/com/mdt/rtm/data/RtmTask.java
new file mode 100644
index 000000000..e35c81676
--- /dev/null
+++ b/src/com/mdt/rtm/data/RtmTask.java
@@ -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 .
+ */
+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 + ">";
+ }
+
+}
diff --git a/src/com/mdt/rtm/data/RtmTaskList.java b/src/com/mdt/rtm/data/RtmTaskList.java
new file mode 100644
index 000000000..c6b68e37a
--- /dev/null
+++ b/src/com/mdt/rtm/data/RtmTaskList.java
@@ -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 .
+ */
+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 series;
+
+ public RtmTaskList(String id) {
+ this.id = id;
+ this.series = new ArrayList();
+ }
+
+ public RtmTaskList(Element elt) {
+ id = elt.getAttribute("id");
+ series = new ArrayList();
+ 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 getSeries() {
+ return Collections.unmodifiableList(series);
+ }
+}
diff --git a/src/com/mdt/rtm/data/RtmTaskNote.java b/src/com/mdt/rtm/data/RtmTaskNote.java
new file mode 100644
index 000000000..837f24569
--- /dev/null
+++ b/src/com/mdt/rtm/data/RtmTaskNote.java
@@ -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 .
+ */
+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;
+ }
+
+}
diff --git a/src/com/mdt/rtm/data/RtmTaskNotes.java b/src/com/mdt/rtm/data/RtmTaskNotes.java
new file mode 100644
index 000000000..c63430932
--- /dev/null
+++ b/src/com/mdt/rtm/data/RtmTaskNotes.java
@@ -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 .
+ */
+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 notes;
+
+ public RtmTaskNotes(Element element)
+ {
+ notes = new ArrayList();
+ for (Element child : children(element, "note"))
+ {
+ notes.add(new RtmTaskNote(child));
+ }
+ }
+
+ public List getNotes()
+ {
+ return notes;
+ }
+
+}
diff --git a/src/com/mdt/rtm/data/RtmTaskSeries.java b/src/com/mdt/rtm/data/RtmTaskSeries.java
new file mode 100644
index 000000000..9c0b802fe
--- /dev/null
+++ b/src/com/mdt/rtm/data/RtmTaskSeries.java
@@ -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 .
+ */
+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;
+ }
+
+}
diff --git a/src/com/mdt/rtm/data/RtmTasks.java b/src/com/mdt/rtm/data/RtmTasks.java
new file mode 100644
index 000000000..c55b0e7ed
--- /dev/null
+++ b/src/com/mdt/rtm/data/RtmTasks.java
@@ -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 .
+ */
+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 lists;
+
+ public RtmTasks() {
+ this.lists = new ArrayList();
+ }
+
+ public RtmTasks(Element elt) {
+ this.lists = new ArrayList();
+ for (Element listElt : children(elt, "list")) {
+ lists.add(new RtmTaskList(listElt));
+ }
+ }
+
+ public List getLists() {
+ return Collections.unmodifiableList(lists);
+ }
+}
diff --git a/src/com/mdt/rtm/data/RtmTimeline.java b/src/com/mdt/rtm/data/RtmTimeline.java
new file mode 100644
index 000000000..28830c9fc
--- /dev/null
+++ b/src/com/mdt/rtm/data/RtmTimeline.java
@@ -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 .
+ */
+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;
+ }
+}
diff --git a/src/com/mdt/rtm/data/RtmUser.java b/src/com/mdt/rtm/data/RtmUser.java
new file mode 100644
index 000000000..236ef27bc
--- /dev/null
+++ b/src/com/mdt/rtm/data/RtmUser.java
@@ -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 .
+ */
+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;
+ }
+
+}
diff --git a/src/com/timsu/astrid/activities/TaskEdit.java b/src/com/timsu/astrid/activities/TaskEdit.java
index 5a3e42155..31c6a144e 100644
--- a/src/com/timsu/astrid/activities/TaskEdit.java
+++ b/src/com/timsu/astrid/activities/TaskEdit.java
@@ -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 {
@@ -99,11 +107,14 @@ public class TaskEdit extends TaskModificationTabbedActivity {
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 tags;
@@ -119,23 +130,29 @@ public class TaskEdit extends TaskModificationTabbedActivity {
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 {
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 {
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 {
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 {
R.layout.importance_spinner_dropdown,
Importance.values());
importance.setAdapter(importanceAdapter);
+ ArrayAdapter repeatAdapter = new ArrayAdapter(
+ 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 {
}
});
+ 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 {
.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 {
}
/** 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);
diff --git a/src/com/timsu/astrid/activities/TaskList.java b/src/com/timsu/astrid/activities/TaskList.java
index 9987acdcf..5505db9a0 100644
--- a/src/com/timsu/astrid/activities/TaskList.java
+++ b/src/com/timsu/astrid/activities/TaskList.java
@@ -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 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());
diff --git a/src/com/timsu/astrid/activities/TaskListAdapter.java b/src/com/timsu/astrid/activities/TaskListAdapter.java
index c0e2c23fd..36a004373 100644
--- a/src/com/timsu/astrid/activities/TaskListAdapter.java
+++ b/src/com/timsu/astrid/activities/TaskListAdapter.java
@@ -184,7 +184,6 @@ public class TaskListAdapter extends ArrayAdapter {
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 {
if(newProgressPercentage != task.getProgressPercentage()) {
setTaskProgress(task, view, newProgressPercentage);
- setTaskAppearance(task, name, progress);
+ setupView(view, task);
}
}
});
diff --git a/src/com/timsu/astrid/activities/TaskViewNotifier.java b/src/com/timsu/astrid/activities/TaskViewNotifier.java
index 9ae2df3b9..f3cbfe9b6 100644
--- a/src/com/timsu/astrid/activities/TaskViewNotifier.java
+++ b/src/com/timsu/astrid/activities/TaskViewNotifier.java
@@ -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();
}
}
diff --git a/src/com/timsu/astrid/data/enums/RepeatInterval.java b/src/com/timsu/astrid/data/enums/RepeatInterval.java
new file mode 100644
index 000000000..12b9e8837
--- /dev/null
+++ b/src/com/timsu/astrid/data/enums/RepeatInterval.java
@@ -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;
+ }
+}
diff --git a/src/com/timsu/astrid/data/task/AbstractTaskModel.java b/src/com/timsu/astrid/data/task/AbstractTaskModel.java
index fa5ca2b8f..b62820cc9 100644
--- a/src/com/timsu/astrid/data/task/AbstractTaskModel.java
+++ b/src/com/timsu/astrid/data/task/AbstractTaskModel.java
@@ -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) {
diff --git a/src/com/timsu/astrid/data/task/TaskController.java b/src/com/timsu/astrid/data/task/TaskController.java
index 0682281c7..2e914fa35 100644
--- a/src/com/timsu/astrid/data/task/TaskController.java
+++ b/src/com/timsu/astrid/data/task/TaskController.java
@@ -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
diff --git a/src/com/timsu/astrid/data/task/TaskModelForEdit.java b/src/com/timsu/astrid/data/task/TaskModelForEdit.java
index af4884bb9..9f28e5c8a 100644
--- a/src/com/timsu/astrid/data/task/TaskModelForEdit.java
+++ b/src/com/timsu/astrid/data/task/TaskModelForEdit.java
@@ -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);
+ }
}
diff --git a/src/com/timsu/astrid/data/task/TaskModelForRepeat.java b/src/com/timsu/astrid/data/task/TaskModelForRepeat.java
new file mode 100644
index 000000000..7d128a4ed
--- /dev/null
+++ b/src/com/timsu/astrid/data/task/TaskModelForRepeat.java
@@ -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);
+ }
+}
diff --git a/src/com/timsu/astrid/sync/Synchronizer.java b/src/com/timsu/astrid/sync/Synchronizer.java
new file mode 100644
index 000000000..c64c760ee
--- /dev/null
+++ b/src/com/timsu/astrid/sync/Synchronizer.java
@@ -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 list : lists.getLists().entrySet()) {
+ Log.i("astrid", "look, " + list.getKey());
+ }
+ } catch (Exception e) {
+ Log.e("astrid", "error parsing", e); // TODO dialog box
+ }
+ }
+}
diff --git a/src/com/timsu/astrid/utilities/Constants.java b/src/com/timsu/astrid/utilities/Constants.java
index 99bb27761..c2cff024d 100644
--- a/src/com/timsu/astrid/utilities/Constants.java
+++ b/src/com/timsu/astrid/utilities/Constants.java
@@ -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;
}
diff --git a/src/com/timsu/astrid/utilities/Preferences.java b/src/com/timsu/astrid/utilities/Preferences.java
index 32165098c..6b7cb5bb6 100644
--- a/src/com/timsu/astrid/utilities/Preferences.java
+++ b/src/com/timsu/astrid/utilities/Preferences.java
@@ -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();
}