diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f34c60e7a..1baed86ab 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -17,5 +17,7 @@
+
+
\ No newline at end of file
diff --git a/res/layout/edit_tag_item.xml b/res/layout/edit_tag_item.xml
new file mode 100644
index 000000000..4f57635e9
--- /dev/null
+++ b/res/layout/edit_tag_item.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/tag_list.xml b/res/layout/tag_list.xml
new file mode 100644
index 000000000..1a28081d8
--- /dev/null
+++ b/res/layout/tag_list.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/res/layout/task_edit.xml b/res/layout/task_edit.xml
index 218367ef4..e12350838 100644
--- a/res/layout/task_edit.xml
+++ b/res/layout/task_edit.xml
@@ -82,7 +82,7 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
-
+
diff --git a/res/layout/task_list_row.xml b/res/layout/task_list_row.xml
index 750581779..d9f83650a 100644
--- a/res/layout/task_list_row.xml
+++ b/res/layout/task_list_row.xml
@@ -9,6 +9,7 @@
android:layout_height="wrap_content">
@@ -18,13 +19,21 @@
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:paddingLeft="5dip"/>
-
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d31919efc..560c30fb5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -17,6 +17,10 @@
- 1 Task
- %d Tasks
+
+ - 1 Tag
+ - %d Tags
+
- 1 Day
@@ -38,6 +42,7 @@
Astrid:
+ Tasks Tagged \"%s\":
hidden
New Task
@@ -96,10 +101,18 @@
Task Notes
Overdue
% of Task Finished
+
+
+
+ Astrid: Tag View:
+ Create Task With Tag
+ Edit Tag
+ Delete Tag
Delete
Delete this task?
+ Remove this tag from all tasks?
diff --git a/src/com/timsu/astrid/activities/TagList.java b/src/com/timsu/astrid/activities/TagList.java
new file mode 100644
index 000000000..a11490628
--- /dev/null
+++ b/src/com/timsu/astrid/activities/TagList.java
@@ -0,0 +1,231 @@
+/*
+ * 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.activities;
+import java.util.List;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnCreateContextMenuListener;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+
+import com.timsu.astrid.R;
+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;
+
+
+/** List all tags and allows a user to see all tasks for a given tag
+ *
+ * @author Tim Su (timsu@stanfordalumni.org)
+ *
+ */
+public class TagList extends Activity {
+ private static final int ACTIVITY_LIST = 0;
+ private static final int ACTIVITY_CREATE = 1;
+
+ private static final int CONTEXT_CREATE_ID = Menu.FIRST + 10;
+ private static final int CONTEXT_DELETE_ID = Menu.FIRST + 11;
+
+ private TagController controller;
+ private ListView listView;
+
+ private List tagArray;
+
+ /** Called when loading up the activity for the first time */
+ private void onLoad() {
+ controller = new TagController(this);
+ controller.open();
+
+ listView = (ListView)findViewById(R.id.taglist);
+
+ fillData();
+ }
+
+ /** Fill in the Tag List with our tags */
+ private void fillData() {
+ Resources r = getResources();
+
+ tagArray = controller.getAllTags();
+
+ // set up the title
+ StringBuilder title = new StringBuilder().
+ append(r.getString(R.string.tagList_titlePrefix)).
+ append(" ").append(r.getQuantityString(R.plurals.Ntags,
+ tagArray.size(), tagArray.size()));
+ setTitle(title);
+
+ // set up our adapter
+ TagListAdapter tagAdapter = new TagListAdapter(this,
+ android.R.layout.simple_list_item_1, tagArray);
+ listView.setAdapter(tagAdapter);
+
+ // list view listener
+ listView.setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view,
+ int position, long id) {
+ TagModelForView tag = (TagModelForView)view.getTag();
+
+ Intent intent = new Intent(TagList.this, TaskList.class);
+ intent.putExtra(TaskList.TAG_TOKEN, tag.
+ getTagIdentifier().getId());
+ startActivityForResult(intent, ACTIVITY_LIST);
+ }
+ });
+
+ listView.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ AdapterContextMenuInfo adapterMenuInfo =
+ (AdapterContextMenuInfo)menuInfo;
+ int position = adapterMenuInfo.position;
+
+ menu.add(position, CONTEXT_CREATE_ID, Menu.NONE,
+ R.string.tagList_context_create);
+ menu.add(position, CONTEXT_DELETE_ID, Menu.NONE,
+ R.string.tagList_context_delete);
+
+ menu.setHeaderTitle(tagArray.get(position).getName());
+ }
+ });
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode,
+ Intent intent) {
+ fillData();
+ }
+
+ // --- list adapter
+
+ private class TagListAdapter extends ArrayAdapter {
+
+ private List objects;
+ private int resource;
+ private LayoutInflater inflater;
+
+ public TagListAdapter(Context context, int resource,
+ List objects) {
+ super(context, resource, objects);
+
+ inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ this.objects = objects;
+ this.resource = resource;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+
+ view = inflater.inflate(resource, parent, false);
+ setupView(view, objects.get(position));
+
+ return view;
+ }
+
+ public void setupView(View view, final TagModelForView tag) {
+ // set up basic properties
+ view.setTag(tag);
+
+ List tasks = controller.getTaggedTasks(
+ tag.getTagIdentifier());
+
+ final TextView name = ((TextView)view.findViewById(android.R.id.text1));
+ name.setText(new StringBuilder(tag.getName()).
+ append(" (").append(tasks.size()).append(")"));
+ }
+ }
+
+ // --- ui control handlers
+
+ private void createTask(TagModelForView tag) {
+ Intent intent = new Intent(this, TaskEdit.class);
+ intent.putExtra(TaskEdit.TAG_NAME_TOKEN, tag.getName());
+ startActivityForResult(intent, ACTIVITY_CREATE);
+ }
+
+ private void deleteTag(final TagIdentifier tagId) {
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.delete_title)
+ .setMessage(R.string.delete_this_tag_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ controller.deleteTag(tagId);
+ fillData();
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ switch(item.getItemId()) {
+ case CONTEXT_CREATE_ID:
+ TagModelForView tag = tagArray.get(item.getGroupId());
+ createTask(tag);
+ return true;
+ case CONTEXT_DELETE_ID:
+ tag = tagArray.get(item.getGroupId());
+ deleteTag(tag.getTagIdentifier());
+ return true;
+ }
+
+ return super.onMenuItemSelected(featureId, item);
+ }
+
+ // --- creating stuff
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.tag_list);
+
+ onLoad();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ controller.close();
+ }
+}
\ No newline at end of file
diff --git a/src/com/timsu/astrid/activities/TaskEdit.java b/src/com/timsu/astrid/activities/TaskEdit.java
index ab1e9f1ef..3ff90ba31 100644
--- a/src/com/timsu/astrid/activities/TaskEdit.java
+++ b/src/com/timsu/astrid/activities/TaskEdit.java
@@ -1,5 +1,5 @@
/*
- * ASTRID: Android's Simple Task Recording Dame
+ * ASTRID: Android's Simple Task Recording Dashboard
*
* Copyright (c) 2009 Tim Su
*
@@ -19,19 +19,20 @@
*/
package com.timsu.astrid.activities;
-import java.text.Format;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import android.app.AlertDialog;
-import android.app.DatePickerDialog;
-import android.app.TimePickerDialog;
-import android.app.DatePickerDialog.OnDateSetListener;
-import android.app.TimePickerDialog.OnTimeSetListener;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -39,32 +40,41 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
-import android.widget.DatePicker;
import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
-import android.widget.TimePicker;
-import android.widget.CompoundButton.OnCheckedChangeListener;
import com.timsu.astrid.R;
import com.timsu.astrid.data.enums.Importance;
+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.utilities.DateUtilities;
-import com.timsu.astrid.widget.NumberPicker;
-import com.timsu.astrid.widget.NumberPickerDialog;
-import com.timsu.astrid.widget.NumberPickerDialog.OnNumberPickedListener;
+import com.timsu.astrid.widget.DateControlSet;
+import com.timsu.astrid.widget.TimeDurationControlSet;
public class TaskEdit extends TaskModificationActivity {
- private static final int SAVE_ID = Menu.FIRST;
- private static final int DISCARD_ID = Menu.FIRST + 1;
- private static final int DELETE_ID = Menu.FIRST + 2;
- public static final int RESULT_DELETE = RESULT_FIRST_USER;
+ // bundle arguments
+ public static final String TAG_NAME_TOKEN = "tag";
+ // menu items
+ private static final int SAVE_ID = Menu.FIRST;
+ private static final int DISCARD_ID = Menu.FIRST + 1;
+ private static final int DELETE_ID = Menu.FIRST + 2;
+
+ // activity results
+ public static final int RESULT_DELETE = RESULT_FIRST_USER;
+
+ // other constants
+ private static final int MAX_TAGS = 5;
+
+ // UI components
private EditText name;
private Spinner importance;
private TimeDurationControlSet estimatedDuration;
@@ -73,19 +83,29 @@ public class TaskEdit extends TaskModificationActivity {
private DateControlSet preferredDueDate;
private DateControlSet hiddenUntil;
private EditText notes;
+ private LinearLayout tagsContainer;
+ // other instance variables
private boolean shouldSaveState = true;
+ private TagController tagController;
+ private List tags;
+ private List taskTags;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ tagController = new TagController(this);
+ tagController.open();
setContentView(R.layout.task_edit);
setUpUIComponents();
setUpListeners();
- populateFields();
- }
+ Bundle extras = getIntent().getExtras();
+ if(extras != null && extras.containsKey(TAG_NAME_TOKEN)) {
+ addTag(extras.getString(TAG_NAME_TOKEN));
+ }
+ }
@Override
protected TaskModelForEdit getModel(TaskIdentifier identifier) {
if (identifier != null)
@@ -94,80 +114,141 @@ public class TaskEdit extends TaskModificationActivity {
return controller.createNewTaskForEdit();
}
- // --- data saving and retrieving
+ /* ======================================================================
+ * =============================================== model reading / saving
+ * ====================================================================== */
private void populateFields() {
Resources r = getResources();
+
+ // set UI components based on model variables
if(model.getCursor() != null)
startManagingCursor(model.getCursor());
-
name.setText(model.getName());
if(model.getName().length() > 0)
setTitle(new StringBuilder().
append(r.getString(R.string.taskEdit_titlePrefix)).
append(" ").
append(model.getName()));
-
estimatedDuration.setTimeElapsed(model.getEstimatedSeconds());
elapsedDuration.setTimeElapsed(model.getElapsedSeconds());
importance.setSelection(model.getImportance().ordinal());
-
definiteDueDate.setDate(model.getDefiniteDueDate());
preferredDueDate.setDate(model.getPreferredDueDate());
hiddenUntil.setDate(model.getHiddenUntil());
-
notes.setText(model.getNotes());
+
+ // tags
+ tags = tagController.getAllTags();
+ if(model.getTaskIdentifier() != null) {
+ taskTags = tagController.getTaskTags(model.getTaskIdentifier());
+ if(taskTags.size() > 0) {
+ Map tagsMap =
+ new HashMap();
+ for(TagModelForView tag : tags)
+ tagsMap.put(tag.getTagIdentifier(), tag);
+ for(TagIdentifier id : taskTags) {
+ if(!tagsMap.containsKey(id))
+ continue;
+
+ TagModelForView tag = tagsMap.get(id.getId());
+ addTag(tag.getName());
+ }
+ }
+ } else
+ taskTags = new LinkedList();
+
+ addTag("");
}
private void save() {
- // usually, user accidentally created a new task
+ // don't save if user accidentally created a new task
if(name.getText().length() == 0)
return;
model.setName(name.getText().toString());
model.setEstimatedSeconds(estimatedDuration.getTimeDurationInSeconds());
model.setElapsedSeconds(elapsedDuration.getTimeDurationInSeconds());
- model.setImportance(Importance.values()[importance.getSelectedItemPosition()]);
-
+ model.setImportance(Importance.values()
+ [importance.getSelectedItemPosition()]);
model.setDefiniteDueDate(definiteDueDate.getDate());
model.setPreferredDueDate(preferredDueDate.getDate());
model.setHiddenUntil(hiddenUntil.getDate());
-
model.setNotes(notes.getText().toString());
try {
- if(!controller.saveTask(model))
- throw new RuntimeException("Unable to save task: false");
- } catch (RuntimeException e) {
- Log.e(getClass().getSimpleName(), "Error saving task!", e);
+ // write out to database
+ controller.saveTask(model);
+ saveTags();
+ } catch (Exception e) {
+ Log.e(getClass().getSimpleName(), "Error saving task!", e); // TODO
+ }
+
+ }
+
+ /** Save task tags. Must be called after task already has an ID */
+ private void saveTags() {
+ Set tagsToDelete;
+ Set tagsToAdd;
+
+ HashSet tagNames = new HashSet();
+ for(int i = 0; i < tagsContainer.getChildCount(); i++) {
+ TextView tagName = (TextView)tagsContainer.getChildAt(i).findViewById(R.id.text1);
+ if(tagName.getText().length() == 0)
+ continue;
+ tagNames.add(tagName.getText().toString());
+ }
+
+ // map names to tag identifiers, creating them if necessary
+ HashSet tagIds = new HashSet();
+ HashMap tagsByName = new HashMap();
+ for(TagModelForView tag : tags)
+ tagsByName.put(tag.getName(), tag.getTagIdentifier());
+ for(String tagName : tagNames) {
+ if(tagsByName.containsKey(tagName))
+ tagIds.add(tagsByName.get(tagName));
+ else {
+ TagIdentifier newTagId = tagController.createTag(tagName);
+ tagIds.add(newTagId);
+ }
}
+
+ tagsToDelete = new HashSet(taskTags);
+ tagsToDelete.removeAll(tagIds);
+ tagsToAdd = tagIds;
+ tagsToAdd.removeAll(taskTags);
+
+ for(TagIdentifier tagId : tagsToDelete)
+ tagController.removeTag(model.getTaskIdentifier(), tagId);
+ for(TagIdentifier tagId : tagsToAdd)
+ tagController.addTag(model.getTaskIdentifier(), tagId);
}
- // --- user interface components
+ /* ======================================================================
+ * ==================================================== UI initialization
+ * ====================================================================== */
+ /** Initialize UI components */
private void setUpUIComponents() {
Resources r = getResources();
setTitle(new StringBuilder()
- .append(r.getString(R.string.app_name))
- .append(": ")
.append(r.getString(R.string.taskEdit_titleGeneric)));
+ // populate instance variables
name = (EditText)findViewById(R.id.name);
importance = (Spinner)findViewById(R.id.importance);
-
- estimatedDuration = new TimeDurationControlSet(R.id.estimatedDuration);
- elapsedDuration = new TimeDurationControlSet(R.id.elapsedDuration);
- definiteDueDate = new DateControlSet(R.id.definiteDueDate_notnull,
+ tagsContainer = (LinearLayout)findViewById(R.id.tags_container);
+ estimatedDuration = new TimeDurationControlSet(this, R.id.estimatedDuration);
+ elapsedDuration = new TimeDurationControlSet(this, R.id.elapsedDuration);
+ definiteDueDate = new DateControlSet(this, R.id.definiteDueDate_notnull,
R.id.definiteDueDate_date, R.id.definiteDueDate_time);
- preferredDueDate = new DateControlSet(R.id.preferredDueDate_notnull,
+ preferredDueDate = new DateControlSet(this, R.id.preferredDueDate_notnull,
R.id.preferredDueDate_date, R.id.preferredDueDate_time);
- hiddenUntil = new DateControlSet(R.id.hiddenUntil_notnull,
+ hiddenUntil = new DateControlSet(this, R.id.hiddenUntil_notnull,
R.id.hiddenUntil_date, R.id.hiddenUntil_time);
-
notes = (EditText)findViewById(R.id.notes);
- // set up for each field
-
+ // individual ui component initialization
ImportanceAdapter importanceAdapter = new ImportanceAdapter(this,
android.R.layout.simple_spinner_item,
R.layout.importance_spinner_dropdown,
@@ -175,61 +256,8 @@ public class TaskEdit extends TaskModificationActivity {
importance.setAdapter(importanceAdapter);
}
- /** Display importance with proper formatting */
- private class ImportanceAdapter extends ArrayAdapter {
- private int textViewResourceId, dropDownResourceId;
- private LayoutInflater inflater;
-
- public ImportanceAdapter(Context context, int textViewResourceId,
- int dropDownResourceId, Importance[] objects) {
- super(context, textViewResourceId, objects);
-
- inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- this.textViewResourceId = textViewResourceId;
- this.dropDownResourceId = dropDownResourceId;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- return getView(position, convertView, parent, textViewResourceId, true);
- }
-
- @Override
- public View getDropDownView(int position, View convertView, ViewGroup parent) {
- return getView(position, convertView, parent, dropDownResourceId, true);
- }
-
- public View getView(int position, View convertView, ViewGroup parent,
- int resource, boolean setColors) {
- View view;
- TextView text;
- Resources r = getResources();
-
- if (convertView == null) {
- view = inflater.inflate(resource, parent, false);
- } else {
- view = convertView;
- }
-
- try {
- text = (TextView) view;
- } catch (ClassCastException e) {
- Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
- throw new IllegalStateException(
- "ArrayAdapter requires the resource ID to be a TextView", e);
- }
-
- text.setText(r.getString(getItem(position).getLabelResource()));
- if(setColors)
- text.setBackgroundColor(r.getColor(getItem(position).getColorResource()));
-
- return view;
- }
- }
-
/** Set up button listeners */
private void setUpListeners() {
-
Button saveButton = (Button) findViewById(R.id.save);
saveButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
@@ -247,13 +275,70 @@ public class TaskEdit extends TaskModificationActivity {
Button deleteButton = (Button) findViewById(R.id.delete);
if(model.getTaskIdentifier() == null)
deleteButton.setVisibility(View.GONE);
- deleteButton.setOnClickListener(new View.OnClickListener() {
- public void onClick(View view) {
- deleteButtonClick();
+ else {
+ deleteButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View view) {
+ deleteButtonClick();
+ }
+ });
+ }
+ }
+
+ /** Adds a tag to the tag field */
+ private boolean addTag(String tagName) {
+ if (tagsContainer.getChildCount() >= MAX_TAGS) {
+ return false;
+ }
+
+ LayoutInflater inflater = getLayoutInflater();
+ final View tagItem = inflater.inflate(R.layout.edit_tag_item, null);
+ tagsContainer.addView(tagItem);
+
+ AutoCompleteTextView textView = (AutoCompleteTextView)tagItem.
+ findViewById(R.id.text1);
+ textView.setText(tagName);
+ ArrayAdapter tagsAdapter =
+ new ArrayAdapter(this,
+ android.R.layout.simple_dropdown_item_1line, tags);
+ textView.setAdapter(tagsAdapter);
+ textView.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before,
+ int count) {
+ if(start == 0 && tagsContainer.getChildAt(
+ tagsContainer.getChildCount()-1) == tagItem) {
+ addTag("");
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ //
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count,
+ int after) {
+ //
}
});
+
+ ImageButton reminderRemoveButton;
+ reminderRemoveButton = (ImageButton)tagItem.findViewById(R.id.button1);
+ reminderRemoveButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ tagsContainer.removeView(tagItem);
+ }
+ });
+
+ return true;
}
+ /* ======================================================================
+ * ======================================================= event handlers
+ * ====================================================================== */
+
private void saveButtonClick() {
setResult(RESULT_OK);
finish();
@@ -342,136 +427,65 @@ public class TaskEdit extends TaskModificationActivity {
populateFields();
}
- // --- date/time methods and helper classes
-
- private class TimeDurationControlSet implements OnNumberPickedListener,
- View.OnClickListener {
- private Button timeButton;
- private int timeDuration;
- private final NumberPickerDialog dialog =
- new NumberPickerDialog(TaskEdit.this, this,
- getResources().getString(R.string.minutes_dialog),
- 0, 5, 0, 999);
-
- public TimeDurationControlSet(int timeButtonId) {
- timeButton = (Button)findViewById(timeButtonId);
- timeButton.setOnClickListener(this);
- }
-
- public int getTimeDurationInSeconds() {
- return timeDuration;
- }
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ tagController.close();
+ }
- public void setTimeElapsed(Integer timeDurationInSeconds) {
- if(timeDurationInSeconds == null)
- timeDurationInSeconds = 0;
+ /* ======================================================================
+ * ========================================== UI component helper classes
+ * ====================================================================== */
- timeDuration = timeDurationInSeconds;
+ /** Adapter with custom view to display Importance with proper formatting */
+ private class ImportanceAdapter extends ArrayAdapter {
+ private int textViewResourceId, dropDownResourceId;
+ private LayoutInflater inflater;
- Resources r = getResources();
- if(timeDurationInSeconds == 0) {
- timeButton.setText(r.getString(R.string.blank_button_title));
- return;
- }
+ public ImportanceAdapter(Context context, int textViewResourceId,
+ int dropDownResourceId, Importance[] objects) {
+ super(context, textViewResourceId, objects);
- timeButton.setText(DateUtilities.getDurationString(r,
- timeDurationInSeconds, 2));
- dialog.setInitialValue(timeDuration/60);
+ inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ this.textViewResourceId = textViewResourceId;
+ this.dropDownResourceId = dropDownResourceId;
}
@Override
- /** Called when NumberPicker activity is completed */
- public void onNumberPicked(NumberPicker view, int value) {
- setTimeElapsed(value * 60);
- }
-
- /** Called when time button is clicked */
- public void onClick(View v) {
- dialog.show();
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return getView(position, convertView, parent, textViewResourceId, true);
}
-
- }
-
- private static final Format dateFormatter = new SimpleDateFormat("EEE, MMM d, yyyy");
- private static final Format timeFormatter = new SimpleDateFormat("h:mm a");
-
- private class DateControlSet implements OnTimeSetListener,
- OnDateSetListener, View.OnClickListener {
- private CheckBox activatedCheckBox;
- private Button dateButton;
- private Button timeButton;
- private Date date;
-
- public DateControlSet(int checkBoxId, int dateButtonId, int timeButtonId) {
- activatedCheckBox = (CheckBox)findViewById(checkBoxId);
- dateButton = (Button)findViewById(dateButtonId);
- timeButton = (Button)findViewById(timeButtonId);
-
- activatedCheckBox.setOnCheckedChangeListener(
- new OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView,
- boolean isChecked) {
- dateButton.setEnabled(isChecked);
- timeButton.setEnabled(isChecked);
- }
- });
- dateButton.setOnClickListener(this);
- timeButton.setOnClickListener(this);
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ return getView(position, convertView, parent, dropDownResourceId, true);
}
- public Date getDate() {
- if(!activatedCheckBox.isChecked())
- return null;
- return date;
- }
+ public View getView(int position, View convertView, ViewGroup parent,
+ int resource, boolean setColors) {
+ View view;
+ TextView text;
+ Resources r = getResources();
- /** Initialize the components for the given date field */
- public void setDate(Date newDate) {
- this.date = newDate;
- if(newDate == null) {
- date = new Date();
- date.setMinutes(0);
+ if (convertView == null) {
+ view = inflater.inflate(resource, parent, false);
+ } else {
+ view = convertView;
}
- activatedCheckBox.setChecked(newDate != null);
- dateButton.setEnabled(newDate != null);
- timeButton.setEnabled(newDate != null);
-
- updateDate();
- updateTime();
- }
-
- public void onDateSet(DatePicker view, int year, int month, int monthDay) {
- date.setYear(year - 1900);
- date.setMonth(month);
- date.setDate(monthDay);
- updateDate();
- }
-
- public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
- date.setHours(hourOfDay);
- date.setMinutes(minute);
- updateTime();
- }
-
- public void updateDate() {
- dateButton.setText(dateFormatter.format(date));
-
- }
+ try {
+ text = (TextView) view;
+ } catch (ClassCastException e) {
+ Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
+ throw new IllegalStateException(
+ "ArrayAdapter requires the resource ID to be a TextView", e);
+ }
- public void updateTime() {
- timeButton.setText(timeFormatter.format(date));
- }
+ text.setText(r.getString(getItem(position).getLabelResource()));
+ if(setColors)
+ text.setBackgroundColor(r.getColor(getItem(position).getColorResource()));
- public void onClick(View v) {
- if(v == timeButton)
- new TimePickerDialog(TaskEdit.this, this, date.getHours(),
- date.getMinutes(), false).show();
- else
- new DatePickerDialog(TaskEdit.this, this, 1900 +
- date.getYear(), date.getMonth(), date.getDate()).show();
+ return view;
}
}
}
diff --git a/src/com/timsu/astrid/activities/TaskList.java b/src/com/timsu/astrid/activities/TaskList.java
index b3f68d9cd..e9a007bf7 100644
--- a/src/com/timsu/astrid/activities/TaskList.java
+++ b/src/com/timsu/astrid/activities/TaskList.java
@@ -19,7 +19,9 @@
*/
package com.timsu.astrid.activities;
import java.util.Date;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import android.app.Activity;
import android.app.AlertDialog;
@@ -49,6 +51,9 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.CompoundButton.OnCheckedChangeListener;
import com.timsu.astrid.R;
+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.TaskController;
import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.data.task.TaskModelForList;
@@ -61,36 +66,56 @@ import com.timsu.astrid.data.task.TaskModelForList;
*
*/
public class TaskList extends Activity {
+
+ // bundle tokens
+ public static final String TAG_TOKEN = "tag";
+
+ // activities
private static final int ACTIVITY_CREATE = 0;
private static final int ACTIVITY_VIEW = 1;
private static final int ACTIVITY_EDIT = 2;
+ private static final int ACTIVITY_TAGS = 3;
+ // menu ids
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 CONTEXT_EDIT_ID = Menu.FIRST + 10;
private static final int CONTEXT_DELETE_ID = Menu.FIRST + 11;
private static final int CONTEXT_TIMER_ID = Menu.FIRST + 12;
-
private static final int CONTEXT_FILTER_HIDDEN = Menu.FIRST + 20;
private static final int CONTEXT_FILTER_DONE = Menu.FIRST + 21;
+ // ui components
private TaskController controller;
+ private TagController tagController = null;
private ListView listView;
private Button addButton;
+ // other instance variables
private List taskArray;
+ private Map tagMap;
private boolean filterShowHidden = false;
private boolean filterShowDone = false;
+ private TagModelForView filterTag = null;
/** Called when loading up the activity for the first time */
private void onLoad() {
controller = new TaskController(this);
controller.open();
+ tagController = new TagController(this);
+ tagController.open();
- listView = (ListView)findViewById(R.id.tasklist);
+ tagMap = tagController.getAllTagsAsMap();
+ // check if we want to filter by tag
+ Bundle extras = getIntent().getExtras();
+ if(extras != null && extras.containsKey(TAG_TOKEN)) {
+ TagIdentifier identifier = new TagIdentifier(extras.getLong(TAG_TOKEN));
+ filterTag = tagMap.get(identifier);
+ }
+
+ listView = (ListView)findViewById(R.id.tasklist);
addButton = (Button)findViewById(R.id.addtask);
addButton.setOnClickListener(new
View.OnClickListener() {
@@ -129,17 +154,25 @@ public class TaskList extends Activity {
private void fillData() {
Resources r = getResources();
- // get the database cursor
Cursor tasksCursor;
- if(filterShowDone)
- tasksCursor = controller.getAllTaskListCursor();
- else
- tasksCursor = controller.getActiveTaskListCursor();
+
+ // get the array of tasks
+ if(filterTag != null) {
+ List tasks = tagController.getTaggedTasks(
+ filterTag.getTagIdentifier());
+
+ tasksCursor = controller.getTaskListCursorById(tasks);
+ } else {
+ if(filterShowDone)
+ tasksCursor = controller.getAllTaskListCursor();
+ else
+ tasksCursor = controller.getActiveTaskListCursor();
+ }
startManagingCursor(tasksCursor);
- int totalTasks = tasksCursor.getCount();
- taskArray = controller.createTaskListFromCursor(tasksCursor, !filterShowHidden);
- int hiddenTasks = totalTasks - taskArray.size();
+ taskArray = controller.createTaskListFromCursor(tasksCursor,
+ !filterShowHidden);
+ int hiddenTasks = tasksCursor.getCount() - taskArray.size();
// hide "add" button if we have a few tasks
if(taskArray.size() > 2)
@@ -147,8 +180,12 @@ public class TaskList extends Activity {
// set up the title
StringBuilder title = new StringBuilder().
- append(r.getString(R.string.taskList_titlePrefix)).
- append(" ").append(r.getQuantityString(R.plurals.Ntasks,
+ append(r.getString(R.string.taskList_titlePrefix)).append(" ");
+ if(filterTag != null) {
+ title.append(r.getString(R.string.taskList_titleTagPrefix,
+ filterTag.getName())).append(" ");
+ }
+ title.append(r.getQuantityString(R.plurals.Ntasks,
taskArray.size(), taskArray.size()));
if(hiddenTasks > 0)
title.append(" (").append(hiddenTasks).append(" ").
@@ -215,6 +252,7 @@ public class TaskList extends Activity {
// set up basic properties
final TextView name = ((TextView)view.findViewById(R.id.text1));
+ final TextView properties = ((TextView)view.findViewById(R.id.text2));
final CheckBox progress = ((CheckBox)view.findViewById(R.id.cb1));
final ImageView timer = ((ImageView)view.findViewById(R.id.image1));
@@ -226,6 +264,21 @@ public class TaskList extends Activity {
if(task.getTimerStart() != null)
timer.setImageDrawable(r.getDrawable(R.drawable.ic_dialog_time));
progress.setChecked(task.isTaskCompleted());
+
+ List tags = tagController.getTaskTags(
+ task.getTaskIdentifier());
+ StringBuilder tagString = new StringBuilder();
+ for(Iterator i = tags.iterator(); i.hasNext(); ) {
+ TagModelForView tag = tagMap.get(i.next());
+ tagString.append(tag.getName());
+ if(i.hasNext())
+ tagString.append(", ");
+ }
+ if(tagString.length() > 0)
+ properties.setText(tagString);
+ else
+ properties.setVisibility(View.GONE);
+
setTaskAppearance(name, task);
}
@@ -302,8 +355,10 @@ public class TaskList extends Activity {
// --- ui control handlers
private void createTask() {
- Intent i = new Intent(this, TaskEdit.class);
- startActivityForResult(i, ACTIVITY_CREATE);
+ Intent intent = new Intent(this, TaskEdit.class);
+ if(filterTag != null)
+ intent.putExtra(TaskEdit.TAG_NAME_TOKEN, filterTag.getName());
+ startActivityForResult(intent, ACTIVITY_CREATE);
}
private void deleteTask(final TaskIdentifier taskId) {
@@ -325,6 +380,9 @@ public class TaskList extends Activity {
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ Intent intent;
+ TaskModelForList task;
+
switch(item.getItemId()) {
case INSERT_ID:
createTask();
@@ -333,12 +391,17 @@ public class TaskList extends Activity {
listView.showContextMenu();
return true;
case TAGS_ID:
- // TODO
+ if(filterTag == null) {
+ intent = new Intent(TaskList.this, TagList.class);
+ startActivityForResult(intent, ACTIVITY_TAGS);
+ } else {
+ finish();
+ }
return true;
case CONTEXT_EDIT_ID:
- TaskModelForList task = taskArray.get(item.getGroupId());
- Intent intent = new Intent(TaskList.this, TaskEdit.class);
+ task = taskArray.get(item.getGroupId());
+ intent = new Intent(TaskList.this, TaskEdit.class);
intent.putExtra(TaskEdit.LOAD_INSTANCE_TOKEN, task.getTaskIdentifier().getId());
startActivityForResult(intent, ACTIVITY_EDIT);
return true;
@@ -396,10 +459,10 @@ public class TaskList extends Activity {
item.setIcon(android.R.drawable.ic_menu_view);
item.setAlphabeticShortcut('f');
- /*item = menu.add(Menu.NONE, TAGS_ID, Menu.NONE,
+ item = menu.add(Menu.NONE, TAGS_ID, Menu.NONE,
R.string.taskList_menu_tags);
item.setIcon(android.R.drawable.ic_menu_myplaces);
- item.setAlphabeticShortcut('t');*/
+ item.setAlphabeticShortcut('t');
/*item = menu.add(Menu.NONE, SETTINGS_ID, Menu.NONE,
R.string.taskList_menu_settings);
@@ -410,8 +473,10 @@ public class TaskList extends Activity {
}
@Override
- protected void onStop() {
- super.onStop();
+ protected void onDestroy() {
+ super.onDestroy();
controller.close();
+ if(tagController != null)
+ tagController.close();
}
}
\ No newline at end of file
diff --git a/src/com/timsu/astrid/activities/TaskModificationActivity.java b/src/com/timsu/astrid/activities/TaskModificationActivity.java
index 5999049cf..3d8025f5c 100644
--- a/src/com/timsu/astrid/activities/TaskModificationActivity.java
+++ b/src/com/timsu/astrid/activities/TaskModificationActivity.java
@@ -1,3 +1,22 @@
+/*
+ * 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.activities;
import android.app.Activity;
@@ -25,7 +44,7 @@ public abstract class TaskModificationActivity {
});
}
};
- updateTimer = new Timer();
}
private void setUpListeners() {
@@ -237,7 +236,8 @@ public class TaskView extends TaskModificationActivity {
protected void onResume() {
super.onResume();
populateFields();
- updateTimer.scheduleAtFixedRate(updateTimerTask, 0, 1000); // start timer
+ updateTimer = new Timer(); // start timer
+ updateTimer.scheduleAtFixedRate(updateTimerTask, 0, 1000);
}
// --- event response methods
diff --git a/src/com/timsu/astrid/data/AbstractController.java b/src/com/timsu/astrid/data/AbstractController.java
index dc5d2177a..8663f7e47 100644
--- a/src/com/timsu/astrid/data/AbstractController.java
+++ b/src/com/timsu/astrid/data/AbstractController.java
@@ -19,20 +19,73 @@
*/
package com.timsu.astrid.data;
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Iterator;
+
+import android.app.Activity;
+import android.database.Cursor;
+import android.util.Log;
abstract public class AbstractController {
- protected Context context;
- protected SQLiteDatabase database;
+ protected Activity activity;
// special columns
public static final String KEY_ROWID = "_id";
- // database names
+ // database and table names
protected static final String TASK_TABLE_NAME = "tasks";
+ protected static final String TAG_TABLE_NAME = "tags";
+ protected static final String TAG_TASK_MAP_NAME = "tagTaskMap";
+
+ // cursor iterator
+
+ public static class CursorIterator implements Iterator {
+ Cursor cursor;
+ Class cls;
+
+ public CursorIterator(Cursor cursor, Class cls) {
+ this.cursor = cursor;
+ this.cls = cls;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return !cursor.isLast();
+ }
+
+ @Override
+ public TYPE next() {
+ try {
+ TYPE model = cls.getConstructor(Cursor.class).newInstance(cursor);
+ cursor.moveToNext();
+ return model;
+
+ // ugh...
+ } catch (IllegalArgumentException e) {
+ Log.e("CursorIterator", e.toString());
+ } catch (SecurityException e) {
+ Log.e("CursorIterator", e.toString());
+ } catch (InstantiationException e) {
+ Log.e("CursorIterator", e.toString());
+ } catch (IllegalAccessException e) {
+ Log.e("CursorIterator", e.toString());
+ } catch (InvocationTargetException e) {
+ Log.e("CursorIterator", e.toString());
+ } catch (NoSuchMethodException e) {
+ Log.e("CursorIterator", e.toString());
+ }
+
+ return null;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Can't remove this way");
+ }
+
+ }
}
diff --git a/src/com/timsu/astrid/data/Identifier.java b/src/com/timsu/astrid/data/Identifier.java
new file mode 100644
index 000000000..17c8805db
--- /dev/null
+++ b/src/com/timsu/astrid/data/Identifier.java
@@ -0,0 +1,31 @@
+package com.timsu.astrid.data;
+
+/** Identifier of a single object. Extend this class to create your own */
+public abstract class Identifier {
+ private long id;
+
+ public Identifier(long id) {
+ this.id = id;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public String idAsString() {
+ return Long.toString(id);
+ }
+
+ @Override
+ public int hashCode() {
+ return (int)id;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if(o == null || o.getClass() != getClass())
+ return false;
+
+ return ((Identifier)o).getId() == getId();
+ }
+}
diff --git a/src/com/timsu/astrid/data/tag/AbstractTagModel.java b/src/com/timsu/astrid/data/tag/AbstractTagModel.java
new file mode 100644
index 000000000..75ae137ab
--- /dev/null
+++ b/src/com/timsu/astrid/data/tag/AbstractTagModel.java
@@ -0,0 +1,177 @@
+package com.timsu.astrid.data.tag;
+
+import java.util.Date;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+import com.timsu.astrid.data.AbstractController;
+import com.timsu.astrid.data.AbstractModel;
+
+
+/** Abstract model of a task. Subclasses implement the getters and setters
+ * they are interested in.
+ *
+ * @author timsu
+ *
+ */
+public abstract class AbstractTagModel extends AbstractModel {
+
+ /** Version number of this model */
+ static final int VERSION = 1;
+
+ // field names
+
+ static final String NAME = "name";
+ static final String NOTES = "notes";
+ // reserved fields
+ static final String ICON = "icon";
+ static final String PARENT = "parent";
+ static final String FLAGS = "flags";
+ static final String LOCATION_LAT = "locationLat";
+ static final String LOCATION_LONG = "locationLong";
+ static final String NOTIFICATIONS = "notifications";
+ // end reserved fields
+ static final String CREATION_DATE = "creationDate";
+
+ /** Default values container */
+ private static final ContentValues defaultValues = new ContentValues();
+
+ static {
+ defaultValues.put(NAME, "");
+ defaultValues.put(NOTES, "");
+ defaultValues.put(ICON, (Integer)null);
+ defaultValues.put(PARENT, (Long)null);
+ defaultValues.put(FLAGS, (Integer)0);
+ defaultValues.put(LOCATION_LAT, (Integer)null);
+ defaultValues.put(LOCATION_LONG, (Integer)null);
+ defaultValues.put(NOTIFICATIONS, 0);
+ }
+
+ @Override
+ public ContentValues getDefaultValues() {
+ return defaultValues;
+ }
+
+ // --- database helper
+
+ /** Database Helper manages creating new tables and updating old ones */
+ static class TagModelDatabaseHelper extends SQLiteOpenHelper {
+ String tableName;
+
+ TagModelDatabaseHelper(Context context, String databaseName, String tableName) {
+ super(context, databaseName, null, VERSION);
+ this.tableName = tableName;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ String sql = new StringBuilder().
+ append("CREATE TABLE ").append(tableName).append(" (").
+ append(AbstractController.KEY_ROWID).append(" integer primary key autoincrement, ").
+ append(NAME).append(" text unique not null,").
+ append(NOTES).append(" text not null,").
+ append(ICON).append(" integer,").
+ append(PARENT).append(" integer,").
+ append(FLAGS).append(" integer not null,").
+ append(LOCATION_LAT).append(" integer,").
+ append(LOCATION_LONG).append(" integer,").
+ append(NOTIFICATIONS).append(" integer,").
+ append(CREATION_DATE).append(" integer").
+ append(");").toString();
+ db.execSQL(sql);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.w(getClass().getSimpleName(), "Upgrading database from version " +
+ oldVersion + " to " + newVersion + ".");
+
+ switch(oldVersion) {
+ default:
+ // we don't know how to handle it... do the unfortunate thing
+ Log.e(getClass().getSimpleName(), "Unsupported migration, table dropped!");
+ db.execSQL("DROP TABLE IF EXISTS " + tableName);
+ onCreate(db);
+ }
+ }
+ }
+
+ // --- utility methods
+
+
+
+ // --- identifier
+
+ private TagIdentifier identifier = null;
+
+ public TagIdentifier getTagIdentifier() {
+ return identifier;
+ }
+
+ void setTagIdentifier(TagIdentifier identifier) {
+ this.identifier = identifier;
+ }
+
+ // --- constructor pass-through
+
+ AbstractTagModel() {
+ super();
+ }
+
+ /** Read identifier from database */
+ AbstractTagModel(Cursor cursor) {
+ super(cursor);
+
+ Integer id = retrieveInteger(AbstractController.KEY_ROWID);
+ setTagIdentifier(new TagIdentifier(id));
+ }
+
+ /** Get identifier from argument */
+ AbstractTagModel(TagIdentifier identifier, Cursor cursor) {
+ super(cursor);
+
+ setTagIdentifier(identifier);
+ }
+
+ // --- getters and setters: expose them as you see fit
+
+ protected String getName() {
+ return retrieveString(NAME);
+ }
+
+ protected String getNotes() {
+ return retrieveString(NOTES);
+ }
+
+ protected Date getCreationDate() {
+ return retrieveDate(CREATION_DATE);
+ }
+
+ // --- setters
+
+ protected void setName(String name) {
+ setValues.put(NAME, name);
+ }
+
+ protected void setNotes(String notes) {
+ setValues.put(NOTES, notes);
+ }
+
+ protected void setCreationDate(Date creationDate) {
+ putDate(setValues, CREATION_DATE, creationDate);
+ }
+
+ // --- utility methods
+
+ static void putDate(ContentValues cv, String fieldName, Date date) {
+ if(date == null)
+ cv.put(fieldName, (Long)null);
+ else
+ cv.put(fieldName, date.getTime());
+ }
+}
diff --git a/src/com/timsu/astrid/data/tag/TagController.java b/src/com/timsu/astrid/data/tag/TagController.java
new file mode 100644
index 000000000..fd9cc181c
--- /dev/null
+++ b/src/com/timsu/astrid/data/tag/TagController.java
@@ -0,0 +1,202 @@
+package com.timsu.astrid.data.tag;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import android.app.Activity;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+
+import com.timsu.astrid.data.AbstractController;
+import com.timsu.astrid.data.tag.AbstractTagModel.TagModelDatabaseHelper;
+import com.timsu.astrid.data.tag.TagToTaskMapping.TagToTaskMappingDatabaseHelper;
+import com.timsu.astrid.data.task.TaskIdentifier;
+
+public class TagController extends AbstractController {
+
+ private SQLiteDatabase tagDatabase, tagToTaskMapDatabase;
+
+ // --- tag batch operations
+
+ /** Get a list of all tags */
+ public List getAllTags()
+ throws SQLException {
+ List list = new LinkedList();
+ Cursor cursor = tagDatabase.query(TAG_TABLE_NAME,
+ TagModelForView.FIELD_LIST, null, null, null, null, null, null);
+ activity.startManagingCursor(cursor);
+
+ if(cursor.getCount() == 0)
+ return list;
+ do {
+ cursor.moveToNext();
+ list.add(new TagModelForView(cursor));
+ } while(!cursor.isLast());
+
+ return list;
+ }
+
+ // --- tag to task map batch operations
+
+ /** Get a list of all tags as an id => tag map */
+ public Map getAllTagsAsMap() throws SQLException {
+ Map map = new HashMap();
+ for(TagModelForView tag : getAllTags())
+ map.put(tag.getTagIdentifier(), tag);
+ return map;
+ }
+
+ /** Get a list of tag identifiers for the given task */
+ public List getTaskTags(TaskIdentifier
+ taskId) throws SQLException {
+ List list = new LinkedList();
+ Cursor cursor = tagToTaskMapDatabase.query(TAG_TASK_MAP_NAME,
+ TagToTaskMapping.FIELD_LIST, TagToTaskMapping.TASK + " = ?",
+ new String[] { taskId.idAsString() }, null, null, null);
+ activity.startManagingCursor(cursor);
+
+ if(cursor.getCount() == 0)
+ return list;
+ do {
+ cursor.moveToNext();
+ list.add(new TagToTaskMapping(cursor).getTag());
+ } while(!cursor.isLast());
+
+ return list;
+ }
+
+ /** Get a list of task identifiers for the given tag */
+ public List getTaggedTasks(TagIdentifier
+ tagId) throws SQLException {
+ List list = new LinkedList();
+ Cursor cursor = tagToTaskMapDatabase.query(TAG_TASK_MAP_NAME,
+ TagToTaskMapping.FIELD_LIST, TagToTaskMapping.TAG + " = ?",
+ new String[] { tagId.idAsString() }, null, null, null);
+ activity.startManagingCursor(cursor);
+
+ if(cursor.getCount() == 0)
+ return list;
+ do {
+ cursor.moveToNext();
+ list.add(new TagToTaskMapping(cursor).getTask());
+ } while(!cursor.isLast());
+
+ return list;
+ }
+
+ // --- single tag operations
+
+ public TagIdentifier createTag(String name) throws SQLException {
+ if(name == null)
+ throw new NullPointerException("Name can't be null");
+
+ TagModelForView newTag = new TagModelForView(name);
+ long row = tagDatabase.insertOrThrow(TAG_TABLE_NAME, AbstractTagModel.NAME,
+ newTag.getMergedValues());
+ return new TagIdentifier(row);
+ }
+
+ /** Creates or saves the given tag */
+ public boolean saveTag(AbstractTagModel tag) throws SQLException {
+ boolean saveSucessful;
+
+ if(tag.getTagIdentifier() == null) {
+ long newRow = tagDatabase.insert(TAG_TABLE_NAME, AbstractTagModel.NAME,
+ tag.getMergedValues());
+ tag.setTagIdentifier(new TagIdentifier(newRow));
+
+ saveSucessful = newRow >= 0;
+ } else {
+ long id = tag.getTagIdentifier().getId();
+ saveSucessful = tagDatabase.update(TAG_TABLE_NAME, tag.getSetValues(),
+ KEY_ROWID + "=" + id, null) > 0;
+ }
+
+ return saveSucessful;
+ }
+
+ /** Returns a TaskModelForView corresponding to the given TaskIdentifier */
+ public TagModelForView fetchTagForView(TagIdentifier tagId) throws SQLException {
+ long id = tagId.getId();
+ Cursor cursor = tagDatabase.query(true, TAG_TABLE_NAME,
+ TagModelForView.FIELD_LIST,
+ KEY_ROWID + "=" + id, null, null, null, null, null);
+
+ if (cursor != null) {
+ cursor.moveToFirst();
+ TagModelForView model = new TagModelForView(cursor);
+ return model;
+ }
+
+ throw new SQLException("Returned empty set!");
+ }
+
+ /** Deletes the tag and removes tag/task mappings */
+ public boolean deleteTag( TagIdentifier tagId)
+ throws SQLException{
+ if(tagToTaskMapDatabase.delete(TAG_TASK_MAP_NAME,
+ TagToTaskMapping.TAG + " = " + tagId.idAsString(), null) < 0)
+ return false;
+
+ return tagDatabase.delete(TAG_TABLE_NAME,
+ KEY_ROWID + " = " + tagId.toString(), null) > 0;
+ }
+
+ // --- single tag to task operations
+
+ /** Remove the given tag from the task */
+ public boolean removeTag(TaskIdentifier taskId, TagIdentifier tagId)
+ throws SQLException{
+ return tagToTaskMapDatabase.delete(TAG_TASK_MAP_NAME,
+ String.format("%s = ? AND %s = ?",
+ TagToTaskMapping.TAG, TagToTaskMapping.TASK),
+ new String[] { tagId.idAsString(), taskId.idAsString() }) > 0;
+ }
+
+ /** Add the given tag to the task */
+ public boolean addTag(TaskIdentifier taskId, TagIdentifier tagId)
+ throws SQLException {
+ ContentValues values = new ContentValues();
+ values.put(TagToTaskMapping.TAG, tagId.getId());
+ values.put(TagToTaskMapping.TASK, taskId.getId());
+ return tagToTaskMapDatabase.insert(TAG_TASK_MAP_NAME, TagToTaskMapping.TAG,
+ values) >= 0;
+ }
+
+ // --- boilerplate
+
+ /**
+ * Constructor - takes the context to allow the database to be
+ * opened/created
+ */
+ public TagController(Activity activity) {
+ this.activity = activity;
+ }
+
+ /**
+ * Open the notes database. If it cannot be opened, try to create a new
+ * instance of the database. If it cannot be created, throw an exception to
+ * signal the failure
+ *
+ * @return this (self reference, allowing this to be chained in an
+ * initialization call)
+ * @throws SQLException if the database could be neither opened or created
+ */
+ public TagController open() throws SQLException {
+ tagToTaskMapDatabase = new TagToTaskMappingDatabaseHelper(activity,
+ TAG_TASK_MAP_NAME, TAG_TASK_MAP_NAME).getWritableDatabase();
+ tagDatabase = new TagModelDatabaseHelper(activity,
+ TAG_TABLE_NAME, TAG_TABLE_NAME).getWritableDatabase();
+ return this;
+ }
+
+ /** Closes database resource */
+ public void close() {
+ tagDatabase.close();
+ tagToTaskMapDatabase.close();
+ }
+}
diff --git a/src/com/timsu/astrid/data/tag/TagIdentifier.java b/src/com/timsu/astrid/data/tag/TagIdentifier.java
new file mode 100644
index 000000000..7a6f24b51
--- /dev/null
+++ b/src/com/timsu/astrid/data/tag/TagIdentifier.java
@@ -0,0 +1,12 @@
+package com.timsu.astrid.data.tag;
+
+import com.timsu.astrid.data.Identifier;
+
+
+public class TagIdentifier extends Identifier {
+
+ public TagIdentifier(long id) {
+ super(id);
+ }
+
+}
diff --git a/src/com/timsu/astrid/data/tag/TagModelForView.java b/src/com/timsu/astrid/data/tag/TagModelForView.java
new file mode 100644
index 000000000..65b6158a6
--- /dev/null
+++ b/src/com/timsu/astrid/data/tag/TagModelForView.java
@@ -0,0 +1,42 @@
+package com.timsu.astrid.data.tag;
+
+import android.database.Cursor;
+
+import com.timsu.astrid.data.AbstractController;
+
+
+
+/** Fields that you would want to see in the TaskView activity */
+public class TagModelForView extends AbstractTagModel {
+
+ static String[] FIELD_LIST = new String[] {
+ AbstractController.KEY_ROWID,
+ NAME,
+ };
+
+ // --- constructors
+
+ /** Constructor for creating a new model */
+ TagModelForView(String name) {
+ super();
+ setName(name);
+ }
+
+ /** Constructor for getting an existing model */
+ TagModelForView(Cursor cursor) {
+ super(cursor);
+ getName();
+ }
+
+ // --- getters and setters
+
+ @Override
+ public String getName() {
+ return super.getName();
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+}
diff --git a/src/com/timsu/astrid/data/tag/TagToTaskMapping.java b/src/com/timsu/astrid/data/tag/TagToTaskMapping.java
new file mode 100644
index 000000000..867c7c529
--- /dev/null
+++ b/src/com/timsu/astrid/data/tag/TagToTaskMapping.java
@@ -0,0 +1,116 @@
+package com.timsu.astrid.data.tag;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+import com.timsu.astrid.data.AbstractController;
+import com.timsu.astrid.data.AbstractModel;
+import com.timsu.astrid.data.task.TaskIdentifier;
+
+
+/** A single tag on a task
+ *
+ * @author timsu
+ *
+ */
+public class TagToTaskMapping extends AbstractModel {
+
+ /** Version number of this model */
+ static final int VERSION = 2;
+
+ // field names
+
+ static final String TASK = "task";
+ static final String TAG = "tag";
+
+ /** Default values container */
+ private static final ContentValues defaultValues = new ContentValues();
+
+ @Override
+ public ContentValues getDefaultValues() {
+ return defaultValues;
+ }
+
+ static String[] FIELD_LIST = new String[] {
+ AbstractController.KEY_ROWID,
+ TASK,
+ TAG,
+ };
+
+ // --- database helper
+
+ /** Database Helper manages creating new tables and updating old ones */
+ static class TagToTaskMappingDatabaseHelper extends SQLiteOpenHelper {
+ String tableName;
+
+ TagToTaskMappingDatabaseHelper(Context context, String databaseName, String tableName) {
+ super(context, databaseName, null, VERSION);
+ this.tableName = tableName;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ String sql = new StringBuilder().
+ append("CREATE TABLE ").append(tableName).append(" (").
+ append(AbstractController.KEY_ROWID).append(" integer primary key autoincrement, ").
+ append(TASK).append(" integer not null,").
+ append(TAG).append(" integer not null,").
+ append("unique (").append(TASK).append(",").append(TAG).append(")").
+ append(");").toString();
+ db.execSQL(sql);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.w(getClass().getSimpleName(), "Upgrading database from version " +
+ oldVersion + " to " + newVersion + ".");
+
+ switch(oldVersion) {
+ default:
+ // we don't know how to handle it... do the unfortunate thing
+ Log.e(getClass().getSimpleName(), "Unsupported migration, table dropped!");
+ db.execSQL("DROP TABLE IF EXISTS " + tableName);
+ onCreate(db);
+ }
+ }
+ }
+
+
+ // --- constructor pass-through
+
+ TagToTaskMapping(TaskIdentifier task, TagIdentifier tag) {
+ super();
+ setTask(task);
+ setTag(tag);
+ }
+
+ TagToTaskMapping(Cursor cursor) {
+ super(cursor);
+ }
+
+ // --- getters and setters: expose them as you see fit
+
+ public boolean isNew() {
+ return getCursor() == null;
+ }
+
+ public TaskIdentifier getTask() {
+ return new TaskIdentifier(retrieveInteger(TASK));
+ }
+
+ public TagIdentifier getTag() {
+ return new TagIdentifier(retrieveInteger(TAG));
+ }
+
+ private void setTask(TaskIdentifier task) {
+ setValues.put(TASK, task.getId());
+ }
+
+ private void setTag(TagIdentifier tag) {
+ setValues.put(TAG, tag.getId());
+ }
+}
diff --git a/src/com/timsu/astrid/data/task/AbstractTaskModel.java b/src/com/timsu/astrid/data/task/AbstractTaskModel.java
index 3ae274d16..8b0d5fd6a 100644
--- a/src/com/timsu/astrid/data/task/AbstractTaskModel.java
+++ b/src/com/timsu/astrid/data/task/AbstractTaskModel.java
@@ -40,8 +40,10 @@ public abstract class AbstractTaskModel extends AbstractModel {
static final String DEFINITE_DUE_DATE = "definiteDueDate";
static final String PREFERRED_DUE_DATE = "preferredDueDate";
static final String HIDDEN_UNTIL = "hiddenUntil";
+ // reserved fields
static final String BLOCKING_ON = "blockingOn";
static final String NOTIFICATIONS = "notifications";
+ // end reserved fields
static final String CREATION_DATE = "creationDate";
static final String COMPLETION_DATE = "completionDate";
@@ -75,10 +77,10 @@ public abstract class AbstractTaskModel extends AbstractModel {
static class TaskModelDatabaseHelper extends SQLiteOpenHelper {
String tableName;
- TaskModelDatabaseHelper(Context context, String databaseName) {
+ TaskModelDatabaseHelper(Context context, String databaseName, String tableName) {
super(context, databaseName, null, VERSION);
- this.tableName = databaseName;
+ this.tableName = tableName;
}
@Override
diff --git a/src/com/timsu/astrid/data/task/TaskController.java b/src/com/timsu/astrid/data/task/TaskController.java
index d8ce91ae7..b9dfbf9b9 100644
--- a/src/com/timsu/astrid/data/task/TaskController.java
+++ b/src/com/timsu/astrid/data/task/TaskController.java
@@ -1,10 +1,12 @@
package com.timsu.astrid.data.task;
+import java.util.ArrayList;
import java.util.List;
-import android.content.Context;
+import android.app.Activity;
import android.database.Cursor;
import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.timsu.astrid.data.AbstractController;
@@ -12,6 +14,8 @@ import com.timsu.astrid.data.task.AbstractTaskModel.TaskModelDatabaseHelper;
public class TaskController extends AbstractController {
+ private SQLiteDatabase database;
+
// --- task list operations
/** Return a list of all of the tasks with progress < COMPLETE_PERCENTAGE */
@@ -31,7 +35,38 @@ public class TaskController extends AbstractController {
/** Create a weighted list of tasks from the db cursor given */
public List createTaskListFromCursor(Cursor cursor,
boolean hideHidden) {
- return TaskModelForList.createTaskModelList(cursor, hideHidden);
+ List list = new ArrayList();
+
+ if(cursor.getCount() == 0)
+ return list;
+
+ do {
+ cursor.moveToNext();
+ list.add(new TaskModelForList(cursor));
+ } while(!cursor.isLast());
+
+ return TaskModelForList.sortAndFilterList(list, hideHidden);
+ }
+
+ /** Create a weighted list of tasks from the db cursor given */
+ public Cursor getTaskListCursorById(List idList) {
+
+ StringBuilder where = new StringBuilder();
+ for(int i = 0; i < idList.size(); i++) {
+ where.append(KEY_ROWID);
+ where.append("=");
+ where.append(idList.get(i).toString());
+ if(i < idList.size()-1)
+ where.append(" OR ");
+ }
+
+ // hack for empty arrays
+ if(idList.size() == 0)
+ where.append("0");
+
+ return database.query(true, TASK_TABLE_NAME,
+ TaskModelForList.FIELD_LIST, where.toString(), null, null,
+ null, null, null);
}
// --- single task operations
@@ -74,12 +109,13 @@ public class TaskController extends AbstractController {
}
/** Returns a TaskModelForEdit corresponding to the given TaskIdentifier */
- public TaskModelForEdit fetchTaskForEdit(TaskIdentifier taskId) throws SQLException {
+ public TaskModelForEdit fetchTaskForEdit(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);
-
+ activity.startManagingCursor(cursor);
if (cursor != null) {
cursor.moveToFirst();
TaskModelForEdit model = new TaskModelForEdit(taskId, cursor);
@@ -97,7 +133,7 @@ public class TaskController extends AbstractController {
Cursor cursor = database.query(true, TASK_TABLE_NAME,
TaskModelForView.FIELD_LIST,
KEY_ROWID + "=" + id, null, null, null, null, null);
-
+ activity.startManagingCursor(cursor);
if (cursor != null) {
cursor.moveToFirst();
TaskModelForView model = new TaskModelForView(taskId, cursor);
@@ -114,8 +150,8 @@ public class TaskController extends AbstractController {
* Constructor - takes the context to allow the database to be
* opened/created
*/
- public TaskController(Context context) {
- this.context = context;
+ public TaskController(Activity activity) {
+ this.activity = activity;
}
/**
@@ -129,7 +165,7 @@ public class TaskController extends AbstractController {
*/
public TaskController open() throws SQLException {
SQLiteOpenHelper databaseHelper = new TaskModelDatabaseHelper(
- context, TASK_TABLE_NAME);
+ activity, TASK_TABLE_NAME, TASK_TABLE_NAME);
database = databaseHelper.getWritableDatabase();
return this;
}
diff --git a/src/com/timsu/astrid/data/task/TaskIdentifier.java b/src/com/timsu/astrid/data/task/TaskIdentifier.java
index 5d1135789..a1c3c0741 100644
--- a/src/com/timsu/astrid/data/task/TaskIdentifier.java
+++ b/src/com/timsu/astrid/data/task/TaskIdentifier.java
@@ -1,15 +1,11 @@
package com.timsu.astrid.data.task;
+import com.timsu.astrid.data.Identifier;
-/** A little class that identifies a task. For saving state and passing around */
-public class TaskIdentifier {
- private long id;
- public TaskIdentifier(long id) {
- this.id = id;
- }
+public class TaskIdentifier extends Identifier {
- public long getId() {
- return id;
+ public TaskIdentifier(long id) {
+ super(id);
}
}
diff --git a/src/com/timsu/astrid/data/task/TaskModelForList.java b/src/com/timsu/astrid/data/task/TaskModelForList.java
index ece2886c6..650646f84 100644
--- a/src/com/timsu/astrid/data/task/TaskModelForList.java
+++ b/src/com/timsu/astrid/data/task/TaskModelForList.java
@@ -1,10 +1,10 @@
package com.timsu.astrid.data.task;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import android.database.Cursor;
@@ -30,25 +30,24 @@ public class TaskModelForList extends AbstractTaskModel {
HIDDEN_UNTIL,
};
- static List createTaskModelList(Cursor cursor,
- boolean hideHidden) {
- ArrayList list = new ArrayList();
+ /** Takes the incoming list of task models and weights it, removing hidden
+ * tasks if desired. This mutates the list */
+ static List sortAndFilterList(
+ List list, boolean hideHidden) {
final HashMap weights = new
HashMap();
// first, load everything
- for(int i = 0; i < cursor.getCount(); i++) {
- cursor.moveToNext();
- TaskModelForList task = new TaskModelForList(cursor);
-
- // hide tasks
+ for(Iterator i = list.iterator(); i.hasNext(); ) {
+ TaskModelForList task = i.next();
if(hideHidden) {
- if(task.getHiddenUntil() != null &&
- task.getHiddenUntil().getTime() > System.currentTimeMillis())
- continue;
+ if(task.getHiddenUntil() != null && task.getHiddenUntil().
+ getTime() > System.currentTimeMillis()) {
+ i.remove();
+ continue;
+ }
}
- list.add(task);
weights.put(task, task.getWeight());
}
diff --git a/src/com/timsu/astrid/widget/DateControlSet.java b/src/com/timsu/astrid/widget/DateControlSet.java
new file mode 100644
index 000000000..179a53fac
--- /dev/null
+++ b/src/com/timsu/astrid/widget/DateControlSet.java
@@ -0,0 +1,120 @@
+/*
+ * ASTRID: Android's Simple Task Recording Dame
+ *
+ * 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.widget;
+
+import java.text.Format;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import android.app.Activity;
+import android.app.DatePickerDialog;
+import android.app.TimePickerDialog;
+import android.app.DatePickerDialog.OnDateSetListener;
+import android.app.TimePickerDialog.OnTimeSetListener;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.DatePicker;
+import android.widget.TimePicker;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+
+public class DateControlSet implements OnTimeSetListener,
+ OnDateSetListener, View.OnClickListener {
+
+ private static final Format dateFormatter = new SimpleDateFormat("EEE, MMM d, yyyy");
+ private static final Format timeFormatter = new SimpleDateFormat("h:mm a");
+
+ private final Activity activity;
+ private CheckBox activatedCheckBox;
+ private Button dateButton;
+ private Button timeButton;
+ private Date date;
+
+ public DateControlSet(Activity activity, int checkBoxId, int dateButtonId, int timeButtonId) {
+ this.activity = activity;
+ activatedCheckBox = (CheckBox)activity.findViewById(checkBoxId);
+ dateButton = (Button)activity.findViewById(dateButtonId);
+ timeButton = (Button)activity.findViewById(timeButtonId);
+
+ activatedCheckBox.setOnCheckedChangeListener(
+ new OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView,
+ boolean isChecked) {
+ dateButton.setEnabled(isChecked);
+ timeButton.setEnabled(isChecked);
+ }
+ });
+ dateButton.setOnClickListener(this);
+ timeButton.setOnClickListener(this);
+ }
+
+ public Date getDate() {
+ if(!activatedCheckBox.isChecked())
+ return null;
+ return date;
+ }
+
+ /** Initialize the components for the given date field */
+ public void setDate(Date newDate) {
+ this.date = newDate;
+ if(newDate == null) {
+ date = new Date();
+ date.setMinutes(0);
+ }
+
+ activatedCheckBox.setChecked(newDate != null);
+ dateButton.setEnabled(newDate != null);
+ timeButton.setEnabled(newDate != null);
+
+ updateDate();
+ updateTime();
+ }
+
+ public void onDateSet(DatePicker view, int year, int month, int monthDay) {
+ date.setYear(year - 1900);
+ date.setMonth(month);
+ date.setDate(monthDay);
+ updateDate();
+ }
+
+ public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
+ date.setHours(hourOfDay);
+ date.setMinutes(minute);
+ updateTime();
+ }
+
+ public void updateDate() {
+ dateButton.setText(dateFormatter.format(date));
+
+ }
+
+ public void updateTime() {
+ timeButton.setText(timeFormatter.format(date));
+ }
+
+ public void onClick(View v) {
+ if(v == timeButton)
+ new TimePickerDialog(activity, this, date.getHours(),
+ date.getMinutes(), false).show();
+ else
+ new DatePickerDialog(activity, this, 1900 +
+ date.getYear(), date.getMonth(), date.getDate()).show();
+ }
+}
\ No newline at end of file
diff --git a/src/com/timsu/astrid/widget/TimeDurationControlSet.java b/src/com/timsu/astrid/widget/TimeDurationControlSet.java
new file mode 100644
index 000000000..d33a7723c
--- /dev/null
+++ b/src/com/timsu/astrid/widget/TimeDurationControlSet.java
@@ -0,0 +1,79 @@
+/*
+ * ASTRID: Android's Simple Task Recording Dame
+ *
+ * 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.widget;
+
+import android.app.Activity;
+import android.content.res.Resources;
+import android.view.View;
+import android.widget.Button;
+
+import com.timsu.astrid.R;
+import com.timsu.astrid.utilities.DateUtilities;
+import com.timsu.astrid.widget.NumberPickerDialog.OnNumberPickedListener;
+
+public class TimeDurationControlSet implements OnNumberPickedListener,
+ View.OnClickListener {
+
+ private final Activity activity;
+ private Button timeButton;
+ private int timeDuration;
+ private final NumberPickerDialog dialog;
+
+ public TimeDurationControlSet(Activity activity, int timeButtonId) {
+ this.activity = activity;
+ timeButton = (Button)activity.findViewById(timeButtonId);
+ timeButton.setOnClickListener(this);
+ dialog = new NumberPickerDialog(activity, this,
+ activity.getResources().getString(R.string.minutes_dialog),
+ 0, 5, 0, 999);
+ }
+
+ public int getTimeDurationInSeconds() {
+ return timeDuration;
+ }
+
+ public void setTimeElapsed(Integer timeDurationInSeconds) {
+ if(timeDurationInSeconds == null)
+ timeDurationInSeconds = 0;
+
+ timeDuration = timeDurationInSeconds;
+
+ Resources r = activity.getResources();
+ if(timeDurationInSeconds == 0) {
+ timeButton.setText(r.getString(R.string.blank_button_title));
+ return;
+ }
+
+ timeButton.setText(DateUtilities.getDurationString(r,
+ timeDurationInSeconds, 2));
+ dialog.setInitialValue(timeDuration/60);
+ }
+
+ @Override
+ /** Called when NumberPicker activity is completed */
+ public void onNumberPicked(NumberPicker view, int value) {
+ setTimeElapsed(value * 60);
+ }
+
+ /** Called when time button is clicked */
+ public void onClick(View v) {
+ dialog.show();
+ }
+
+
+}
\ No newline at end of file