First pass at adding tagging features to astrid. Add tags from the editor view, also a tags listing page to view the tags you have.

pull/14/head
Tim Su 16 years ago
parent 0af4cf9d99
commit 065fc1a9d4

@ -17,5 +17,7 @@
<activity android:name=".activities.TaskEdit"/> <activity android:name=".activities.TaskEdit"/>
<activity android:name=".activities.TagList"/>
</application> </application>
</manifest> </manifest>

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<AutoCompleteTextView android:id="@+id/text1"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"/>
<ImageButton android:id="@+id/button1"
style="?android:attr/buttonStyleInset"
android:src="@android:drawable/ic_delete"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_marginTop="2dip"
android:layout_marginRight="2dip"
android:layout_marginBottom="2dip"
android:gravity="center_vertical"
/>
</LinearLayout>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:id="@+id/taglist_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView android:id="@+id/taglist"
android:layout_weight="1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

@ -82,7 +82,7 @@
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<!-- <TextView android:id="@+id/tags_label" <TextView android:id="@+id/tags_label"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/tags_label" android:text="@string/tags_label"
@ -92,7 +92,7 @@
android:orientation="vertical" android:orientation="vertical"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
</LinearLayout> --> </LinearLayout>
</LinearLayout> </LinearLayout>
<!-- DATES --> <!-- DATES -->

@ -9,6 +9,7 @@
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<CheckBox android:id="@+id/cb1" <CheckBox android:id="@+id/cb1"
android:gravity="center_vertical"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingLeft="5dip"/> android:paddingLeft="5dip"/>
@ -21,10 +22,18 @@
<TextView android:id="@+id/text1" <TextView android:id="@+id/text1"
android:layout_height="?android:attr/listPreferredItemHeight" android:layout_height="?android:attr/listPreferredItemHeight"
android:layout_width="fill_parent" android:layout_width="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge" android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical" android:gravity="center_vertical"
android:paddingLeft="5dip" android:paddingLeft="5dip"
android:singleLine="true"/> android:singleLine="true"/>
<TextView android:id="@+id/text2"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:textAppearance="?android:attr/textAppearanceSmall"
android:gravity="center_vertical"
android:paddingLeft="15dip"
android:singleLine="true"/>
</LinearLayout> </LinearLayout>

@ -17,6 +17,10 @@
<item quantity="one">1 Task</item> <item quantity="one">1 Task</item>
<item quantity="other">%d Tasks</item> <item quantity="other">%d Tasks</item>
</plurals> </plurals>
<plurals name="Ntags">
<item quantity="one">1 Tag</item>
<item quantity="other">%d Tags</item>
</plurals>
<!-- Time Constants --> <!-- Time Constants -->
<plurals name="Ndays"> <plurals name="Ndays">
<item quantity="one">1 Day</item> <item quantity="one">1 Day</item>
@ -38,6 +42,7 @@
<!-- TaskList --> <!-- TaskList -->
<skip /> <skip />
<string name="taskList_titlePrefix">Astrid: </string> <string name="taskList_titlePrefix">Astrid: </string>
<string name="taskList_titleTagPrefix">Tasks Tagged \"%s\": </string>
<string name="taskList_hiddenSuffix"> hidden</string> <string name="taskList_hiddenSuffix"> hidden</string>
<string name="addtask_label">New Task</string> <string name="addtask_label">New Task</string>
@ -97,9 +102,17 @@
<string name="overdue_suffix"> Overdue</string> <string name="overdue_suffix"> Overdue</string>
<string name="progress_dialog">% of Task Finished</string> <string name="progress_dialog">% of Task Finished</string>
<!-- TaskList -->
<skip />
<string name="tagList_titlePrefix">Astrid: Tag View: </string>
<string name="tagList_context_create">Create Task With Tag</string>
<string name="tagList_context_edit">Edit Tag</string>
<string name="tagList_context_delete">Delete Tag</string>
<!-- Utilities --> <!-- Utilities -->
<skip /> <skip />
<string name="delete_title">Delete</string> <string name="delete_title">Delete</string>
<string name="delete_this_task_title">Delete this task?</string> <string name="delete_this_task_title">Delete this task?</string>
<string name="delete_this_tag_title">Remove this tag from all tasks?</string>
</resources> </resources>

@ -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<TagModelForView> 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<TagModelForView> {
private List<TagModelForView> objects;
private int resource;
private LayoutInflater inflater;
public TagListAdapter(Context context, int resource,
List<TagModelForView> 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<TaskIdentifier> 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();
}
}

@ -1,5 +1,5 @@
/* /*
* ASTRID: Android's Simple Task Recording Dame * ASTRID: Android's Simple Task Recording Dashboard
* *
* Copyright (c) 2009 Tim Su * Copyright (c) 2009 Tim Su
* *
@ -19,19 +19,20 @@
*/ */
package com.timsu.astrid.activities; package com.timsu.astrid.activities;
import java.text.Format; import java.util.HashMap;
import java.text.SimpleDateFormat; import java.util.HashSet;
import java.util.Date; import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import android.app.AlertDialog; 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.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@ -39,32 +40,41 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button; import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.DatePicker;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import android.widget.TimePicker;
import android.widget.CompoundButton.OnCheckedChangeListener;
import com.timsu.astrid.R; import com.timsu.astrid.R;
import com.timsu.astrid.data.enums.Importance; 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.TaskIdentifier;
import com.timsu.astrid.data.task.TaskModelForEdit; import com.timsu.astrid.data.task.TaskModelForEdit;
import com.timsu.astrid.utilities.DateUtilities; import com.timsu.astrid.widget.DateControlSet;
import com.timsu.astrid.widget.NumberPicker; import com.timsu.astrid.widget.TimeDurationControlSet;
import com.timsu.astrid.widget.NumberPickerDialog;
import com.timsu.astrid.widget.NumberPickerDialog.OnNumberPickedListener;
public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> { public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> {
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 EditText name;
private Spinner importance; private Spinner importance;
private TimeDurationControlSet estimatedDuration; private TimeDurationControlSet estimatedDuration;
@ -73,19 +83,29 @@ public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> {
private DateControlSet preferredDueDate; private DateControlSet preferredDueDate;
private DateControlSet hiddenUntil; private DateControlSet hiddenUntil;
private EditText notes; private EditText notes;
private LinearLayout tagsContainer;
// other instance variables
private boolean shouldSaveState = true; private boolean shouldSaveState = true;
private TagController tagController;
private List<TagModelForView> tags;
private List<TagIdentifier> taskTags;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
tagController = new TagController(this);
tagController.open();
setContentView(R.layout.task_edit); setContentView(R.layout.task_edit);
setUpUIComponents(); setUpUIComponents();
setUpListeners(); setUpListeners();
populateFields();
}
Bundle extras = getIntent().getExtras();
if(extras != null && extras.containsKey(TAG_NAME_TOKEN)) {
addTag(extras.getString(TAG_NAME_TOKEN));
}
}
@Override @Override
protected TaskModelForEdit getModel(TaskIdentifier identifier) { protected TaskModelForEdit getModel(TaskIdentifier identifier) {
if (identifier != null) if (identifier != null)
@ -94,80 +114,141 @@ public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> {
return controller.createNewTaskForEdit(); return controller.createNewTaskForEdit();
} }
// --- data saving and retrieving /* ======================================================================
* =============================================== model reading / saving
* ====================================================================== */
private void populateFields() { private void populateFields() {
Resources r = getResources(); Resources r = getResources();
// set UI components based on model variables
if(model.getCursor() != null) if(model.getCursor() != null)
startManagingCursor(model.getCursor()); startManagingCursor(model.getCursor());
name.setText(model.getName()); name.setText(model.getName());
if(model.getName().length() > 0) if(model.getName().length() > 0)
setTitle(new StringBuilder(). setTitle(new StringBuilder().
append(r.getString(R.string.taskEdit_titlePrefix)). append(r.getString(R.string.taskEdit_titlePrefix)).
append(" "). append(" ").
append(model.getName())); append(model.getName()));
estimatedDuration.setTimeElapsed(model.getEstimatedSeconds()); estimatedDuration.setTimeElapsed(model.getEstimatedSeconds());
elapsedDuration.setTimeElapsed(model.getElapsedSeconds()); elapsedDuration.setTimeElapsed(model.getElapsedSeconds());
importance.setSelection(model.getImportance().ordinal()); importance.setSelection(model.getImportance().ordinal());
definiteDueDate.setDate(model.getDefiniteDueDate()); definiteDueDate.setDate(model.getDefiniteDueDate());
preferredDueDate.setDate(model.getPreferredDueDate()); preferredDueDate.setDate(model.getPreferredDueDate());
hiddenUntil.setDate(model.getHiddenUntil()); hiddenUntil.setDate(model.getHiddenUntil());
notes.setText(model.getNotes()); notes.setText(model.getNotes());
// tags
tags = tagController.getAllTags();
if(model.getTaskIdentifier() != null) {
taskTags = tagController.getTaskTags(model.getTaskIdentifier());
if(taskTags.size() > 0) {
Map<TagIdentifier, TagModelForView> tagsMap =
new HashMap<TagIdentifier, TagModelForView>();
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<TagIdentifier>();
addTag("");
} }
private void save() { 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) if(name.getText().length() == 0)
return; return;
model.setName(name.getText().toString()); model.setName(name.getText().toString());
model.setEstimatedSeconds(estimatedDuration.getTimeDurationInSeconds()); model.setEstimatedSeconds(estimatedDuration.getTimeDurationInSeconds());
model.setElapsedSeconds(elapsedDuration.getTimeDurationInSeconds()); model.setElapsedSeconds(elapsedDuration.getTimeDurationInSeconds());
model.setImportance(Importance.values()[importance.getSelectedItemPosition()]); model.setImportance(Importance.values()
[importance.getSelectedItemPosition()]);
model.setDefiniteDueDate(definiteDueDate.getDate()); model.setDefiniteDueDate(definiteDueDate.getDate());
model.setPreferredDueDate(preferredDueDate.getDate()); model.setPreferredDueDate(preferredDueDate.getDate());
model.setHiddenUntil(hiddenUntil.getDate()); model.setHiddenUntil(hiddenUntil.getDate());
model.setNotes(notes.getText().toString()); model.setNotes(notes.getText().toString());
try { try {
if(!controller.saveTask(model)) // write out to database
throw new RuntimeException("Unable to save task: false"); controller.saveTask(model);
} catch (RuntimeException e) { saveTags();
Log.e(getClass().getSimpleName(), "Error saving task!", e); } 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<TagIdentifier> tagsToDelete;
Set<TagIdentifier> tagsToAdd;
HashSet<String> tagNames = new HashSet<String>();
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<TagIdentifier> tagIds = new HashSet<TagIdentifier>();
HashMap<String, TagIdentifier> tagsByName = new HashMap<String, TagIdentifier>();
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<TagIdentifier>(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() { private void setUpUIComponents() {
Resources r = getResources(); Resources r = getResources();
setTitle(new StringBuilder() setTitle(new StringBuilder()
.append(r.getString(R.string.app_name))
.append(": ")
.append(r.getString(R.string.taskEdit_titleGeneric))); .append(r.getString(R.string.taskEdit_titleGeneric)));
// populate instance variables
name = (EditText)findViewById(R.id.name); name = (EditText)findViewById(R.id.name);
importance = (Spinner)findViewById(R.id.importance); importance = (Spinner)findViewById(R.id.importance);
tagsContainer = (LinearLayout)findViewById(R.id.tags_container);
estimatedDuration = new TimeDurationControlSet(R.id.estimatedDuration); estimatedDuration = new TimeDurationControlSet(this, R.id.estimatedDuration);
elapsedDuration = new TimeDurationControlSet(R.id.elapsedDuration); elapsedDuration = new TimeDurationControlSet(this, R.id.elapsedDuration);
definiteDueDate = new DateControlSet(R.id.definiteDueDate_notnull, definiteDueDate = new DateControlSet(this, R.id.definiteDueDate_notnull,
R.id.definiteDueDate_date, R.id.definiteDueDate_time); 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); 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); R.id.hiddenUntil_date, R.id.hiddenUntil_time);
notes = (EditText)findViewById(R.id.notes); notes = (EditText)findViewById(R.id.notes);
// set up for each field // individual ui component initialization
ImportanceAdapter importanceAdapter = new ImportanceAdapter(this, ImportanceAdapter importanceAdapter = new ImportanceAdapter(this,
android.R.layout.simple_spinner_item, android.R.layout.simple_spinner_item,
R.layout.importance_spinner_dropdown, R.layout.importance_spinner_dropdown,
@ -175,61 +256,8 @@ public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> {
importance.setAdapter(importanceAdapter); importance.setAdapter(importanceAdapter);
} }
/** Display importance with proper formatting */
private class ImportanceAdapter extends ArrayAdapter<Importance> {
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 */ /** Set up button listeners */
private void setUpListeners() { private void setUpListeners() {
Button saveButton = (Button) findViewById(R.id.save); Button saveButton = (Button) findViewById(R.id.save);
saveButton.setOnClickListener(new View.OnClickListener() { saveButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) { public void onClick(View view) {
@ -247,13 +275,70 @@ public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> {
Button deleteButton = (Button) findViewById(R.id.delete); Button deleteButton = (Button) findViewById(R.id.delete);
if(model.getTaskIdentifier() == null) if(model.getTaskIdentifier() == null)
deleteButton.setVisibility(View.GONE); deleteButton.setVisibility(View.GONE);
deleteButton.setOnClickListener(new View.OnClickListener() { else {
public void onClick(View view) { deleteButton.setOnClickListener(new View.OnClickListener() {
deleteButtonClick(); 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<TagModelForView> tagsAdapter =
new ArrayAdapter<TagModelForView>(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() { private void saveButtonClick() {
setResult(RESULT_OK); setResult(RESULT_OK);
finish(); finish();
@ -342,136 +427,65 @@ public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> {
populateFields(); populateFields();
} }
// --- date/time methods and helper classes @Override
protected void onDestroy() {
private class TimeDurationControlSet implements OnNumberPickedListener, super.onDestroy();
View.OnClickListener { tagController.close();
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;
}
public void setTimeElapsed(Integer timeDurationInSeconds) { /* ======================================================================
if(timeDurationInSeconds == null) * ========================================== UI component helper classes
timeDurationInSeconds = 0; * ====================================================================== */
timeDuration = timeDurationInSeconds; /** Adapter with custom view to display Importance with proper formatting */
private class ImportanceAdapter extends ArrayAdapter<Importance> {
private int textViewResourceId, dropDownResourceId;
private LayoutInflater inflater;
Resources r = getResources(); public ImportanceAdapter(Context context, int textViewResourceId,
if(timeDurationInSeconds == 0) { int dropDownResourceId, Importance[] objects) {
timeButton.setText(r.getString(R.string.blank_button_title)); super(context, textViewResourceId, objects);
return;
}
timeButton.setText(DateUtilities.getDurationString(r, inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
timeDurationInSeconds, 2)); this.textViewResourceId = textViewResourceId;
dialog.setInitialValue(timeDuration/60); this.dropDownResourceId = dropDownResourceId;
} }
@Override @Override
/** Called when NumberPicker activity is completed */ public View getView(int position, View convertView, ViewGroup parent) {
public void onNumberPicked(NumberPicker view, int value) { return getView(position, convertView, parent, textViewResourceId, true);
setTimeElapsed(value * 60);
}
/** Called when time button is clicked */
public void onClick(View v) {
dialog.show();
} }
@Override
} public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getView(position, convertView, parent, dropDownResourceId, 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);
} }
public Date getDate() { public View getView(int position, View convertView, ViewGroup parent,
if(!activatedCheckBox.isChecked()) int resource, boolean setColors) {
return null; View view;
return date; TextView text;
} Resources r = getResources();
/** Initialize the components for the given date field */ if (convertView == null) {
public void setDate(Date newDate) { view = inflater.inflate(resource, parent, false);
this.date = newDate; } else {
if(newDate == null) { view = convertView;
date = new Date();
date.setMinutes(0);
} }
activatedCheckBox.setChecked(newDate != null); try {
dateButton.setEnabled(newDate != null); text = (TextView) view;
timeButton.setEnabled(newDate != null); } catch (ClassCastException e) {
Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
updateDate(); throw new IllegalStateException(
updateTime(); "ArrayAdapter requires the resource ID to be a TextView", e);
} }
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() { text.setText(r.getString(getItem(position).getLabelResource()));
timeButton.setText(timeFormatter.format(date)); if(setColors)
} text.setBackgroundColor(r.getColor(getItem(position).getColorResource()));
public void onClick(View v) { return view;
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();
} }
} }
} }

@ -19,7 +19,9 @@
*/ */
package com.timsu.astrid.activities; package com.timsu.astrid.activities;
import java.util.Date; import java.util.Date;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
@ -49,6 +51,9 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.CompoundButton.OnCheckedChangeListener;
import com.timsu.astrid.R; 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.TaskController;
import com.timsu.astrid.data.task.TaskIdentifier; import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.data.task.TaskModelForList; import com.timsu.astrid.data.task.TaskModelForList;
@ -61,36 +66,56 @@ import com.timsu.astrid.data.task.TaskModelForList;
* *
*/ */
public class TaskList extends Activity { 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_CREATE = 0;
private static final int ACTIVITY_VIEW = 1; private static final int ACTIVITY_VIEW = 1;
private static final int ACTIVITY_EDIT = 2; 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 INSERT_ID = Menu.FIRST;
private static final int FILTERS_ID = Menu.FIRST + 1; private static final int FILTERS_ID = Menu.FIRST + 1;
private static final int TAGS_ID = Menu.FIRST + 2; private static final int TAGS_ID = Menu.FIRST + 2;
private static final int CONTEXT_EDIT_ID = Menu.FIRST + 10; 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_DELETE_ID = Menu.FIRST + 11;
private static final int CONTEXT_TIMER_ID = Menu.FIRST + 12; 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_HIDDEN = Menu.FIRST + 20;
private static final int CONTEXT_FILTER_DONE = Menu.FIRST + 21; private static final int CONTEXT_FILTER_DONE = Menu.FIRST + 21;
// ui components
private TaskController controller; private TaskController controller;
private TagController tagController = null;
private ListView listView; private ListView listView;
private Button addButton; private Button addButton;
// other instance variables
private List<TaskModelForList> taskArray; private List<TaskModelForList> taskArray;
private Map<TagIdentifier, TagModelForView> tagMap;
private boolean filterShowHidden = false; private boolean filterShowHidden = false;
private boolean filterShowDone = false; private boolean filterShowDone = false;
private TagModelForView filterTag = null;
/** Called when loading up the activity for the first time */ /** Called when loading up the activity for the first time */
private void onLoad() { private void onLoad() {
controller = new TaskController(this); controller = new TaskController(this);
controller.open(); 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 = (Button)findViewById(R.id.addtask);
addButton.setOnClickListener(new addButton.setOnClickListener(new
View.OnClickListener() { View.OnClickListener() {
@ -129,17 +154,25 @@ public class TaskList extends Activity {
private void fillData() { private void fillData() {
Resources r = getResources(); Resources r = getResources();
// get the database cursor
Cursor tasksCursor; Cursor tasksCursor;
if(filterShowDone)
tasksCursor = controller.getAllTaskListCursor(); // get the array of tasks
else if(filterTag != null) {
tasksCursor = controller.getActiveTaskListCursor(); List<TaskIdentifier> tasks = tagController.getTaggedTasks(
filterTag.getTagIdentifier());
tasksCursor = controller.getTaskListCursorById(tasks);
} else {
if(filterShowDone)
tasksCursor = controller.getAllTaskListCursor();
else
tasksCursor = controller.getActiveTaskListCursor();
}
startManagingCursor(tasksCursor); startManagingCursor(tasksCursor);
int totalTasks = tasksCursor.getCount(); taskArray = controller.createTaskListFromCursor(tasksCursor,
taskArray = controller.createTaskListFromCursor(tasksCursor, !filterShowHidden); !filterShowHidden);
int hiddenTasks = totalTasks - taskArray.size(); int hiddenTasks = tasksCursor.getCount() - taskArray.size();
// hide "add" button if we have a few tasks // hide "add" button if we have a few tasks
if(taskArray.size() > 2) if(taskArray.size() > 2)
@ -147,8 +180,12 @@ public class TaskList extends Activity {
// set up the title // set up the title
StringBuilder title = new StringBuilder(). StringBuilder title = new StringBuilder().
append(r.getString(R.string.taskList_titlePrefix)). append(r.getString(R.string.taskList_titlePrefix)).append(" ");
append(" ").append(r.getQuantityString(R.plurals.Ntasks, 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())); taskArray.size(), taskArray.size()));
if(hiddenTasks > 0) if(hiddenTasks > 0)
title.append(" (").append(hiddenTasks).append(" "). title.append(" (").append(hiddenTasks).append(" ").
@ -215,6 +252,7 @@ public class TaskList extends Activity {
// set up basic properties // set up basic properties
final TextView name = ((TextView)view.findViewById(R.id.text1)); 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 CheckBox progress = ((CheckBox)view.findViewById(R.id.cb1));
final ImageView timer = ((ImageView)view.findViewById(R.id.image1)); final ImageView timer = ((ImageView)view.findViewById(R.id.image1));
@ -226,6 +264,21 @@ public class TaskList extends Activity {
if(task.getTimerStart() != null) if(task.getTimerStart() != null)
timer.setImageDrawable(r.getDrawable(R.drawable.ic_dialog_time)); timer.setImageDrawable(r.getDrawable(R.drawable.ic_dialog_time));
progress.setChecked(task.isTaskCompleted()); progress.setChecked(task.isTaskCompleted());
List<TagIdentifier> tags = tagController.getTaskTags(
task.getTaskIdentifier());
StringBuilder tagString = new StringBuilder();
for(Iterator<TagIdentifier> 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); setTaskAppearance(name, task);
} }
@ -302,8 +355,10 @@ public class TaskList extends Activity {
// --- ui control handlers // --- ui control handlers
private void createTask() { private void createTask() {
Intent i = new Intent(this, TaskEdit.class); Intent intent = new Intent(this, TaskEdit.class);
startActivityForResult(i, ACTIVITY_CREATE); if(filterTag != null)
intent.putExtra(TaskEdit.TAG_NAME_TOKEN, filterTag.getName());
startActivityForResult(intent, ACTIVITY_CREATE);
} }
private void deleteTask(final TaskIdentifier taskId) { private void deleteTask(final TaskIdentifier taskId) {
@ -325,6 +380,9 @@ public class TaskList extends Activity {
@Override @Override
public boolean onMenuItemSelected(int featureId, MenuItem item) { public boolean onMenuItemSelected(int featureId, MenuItem item) {
Intent intent;
TaskModelForList task;
switch(item.getItemId()) { switch(item.getItemId()) {
case INSERT_ID: case INSERT_ID:
createTask(); createTask();
@ -333,12 +391,17 @@ public class TaskList extends Activity {
listView.showContextMenu(); listView.showContextMenu();
return true; return true;
case TAGS_ID: case TAGS_ID:
// TODO if(filterTag == null) {
intent = new Intent(TaskList.this, TagList.class);
startActivityForResult(intent, ACTIVITY_TAGS);
} else {
finish();
}
return true; return true;
case CONTEXT_EDIT_ID: case CONTEXT_EDIT_ID:
TaskModelForList task = taskArray.get(item.getGroupId()); task = taskArray.get(item.getGroupId());
Intent intent = new Intent(TaskList.this, TaskEdit.class); intent = new Intent(TaskList.this, TaskEdit.class);
intent.putExtra(TaskEdit.LOAD_INSTANCE_TOKEN, task.getTaskIdentifier().getId()); intent.putExtra(TaskEdit.LOAD_INSTANCE_TOKEN, task.getTaskIdentifier().getId());
startActivityForResult(intent, ACTIVITY_EDIT); startActivityForResult(intent, ACTIVITY_EDIT);
return true; return true;
@ -396,10 +459,10 @@ public class TaskList extends Activity {
item.setIcon(android.R.drawable.ic_menu_view); item.setIcon(android.R.drawable.ic_menu_view);
item.setAlphabeticShortcut('f'); 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); R.string.taskList_menu_tags);
item.setIcon(android.R.drawable.ic_menu_myplaces); item.setIcon(android.R.drawable.ic_menu_myplaces);
item.setAlphabeticShortcut('t');*/ item.setAlphabeticShortcut('t');
/*item = menu.add(Menu.NONE, SETTINGS_ID, Menu.NONE, /*item = menu.add(Menu.NONE, SETTINGS_ID, Menu.NONE,
R.string.taskList_menu_settings); R.string.taskList_menu_settings);
@ -410,8 +473,10 @@ public class TaskList extends Activity {
} }
@Override @Override
protected void onStop() { protected void onDestroy() {
super.onStop(); super.onDestroy();
controller.close(); controller.close();
if(tagController != null)
tagController.close();
} }
} }

@ -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; package com.timsu.astrid.activities;
import android.app.Activity; import android.app.Activity;
@ -25,7 +44,7 @@ public abstract class TaskModificationActivity<MODEL_TYPE extends
if(savedInstanceState != null && savedInstanceState.containsKey(LOAD_INSTANCE_TOKEN)) { if(savedInstanceState != null && savedInstanceState.containsKey(LOAD_INSTANCE_TOKEN)) {
identifier = new TaskIdentifier(savedInstanceState.getLong( identifier = new TaskIdentifier(savedInstanceState.getLong(
LOAD_INSTANCE_TOKEN)); LOAD_INSTANCE_TOKEN));
} else if(extras != null) } else if(extras != null && extras.containsKey(LOAD_INSTANCE_TOKEN))
identifier = new TaskIdentifier(extras.getLong( identifier = new TaskIdentifier(extras.getLong(
LOAD_INSTANCE_TOKEN)); LOAD_INSTANCE_TOKEN));
@ -35,8 +54,8 @@ public abstract class TaskModificationActivity<MODEL_TYPE extends
abstract protected MODEL_TYPE getModel(TaskIdentifier identifier); abstract protected MODEL_TYPE getModel(TaskIdentifier identifier);
@Override @Override
protected void onStop() { protected void onDestroy() {
super.onStop(); super.onDestroy();
controller.close(); controller.close();
} }

@ -1,5 +1,5 @@
/* /*
* ASTRID: Android's Simple Task Recording Dame * ASTRID: Android's Simple Task Recording Dashboard
* *
* Copyright (c) 2009 Tim Su * Copyright (c) 2009 Tim Su
* *
@ -124,7 +124,6 @@ public class TaskView extends TaskModificationActivity<TaskModelForView> {
}); });
} }
}; };
updateTimer = new Timer();
} }
private void setUpListeners() { private void setUpListeners() {
@ -237,7 +236,8 @@ public class TaskView extends TaskModificationActivity<TaskModelForView> {
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
populateFields(); populateFields();
updateTimer.scheduleAtFixedRate(updateTimerTask, 0, 1000); // start timer updateTimer = new Timer(); // start timer
updateTimer.scheduleAtFixedRate(updateTimerTask, 0, 1000);
} }
// --- event response methods // --- event response methods

@ -19,20 +19,73 @@
*/ */
package com.timsu.astrid.data; package com.timsu.astrid.data;
import android.content.Context; import java.lang.reflect.InvocationTargetException;
import android.database.sqlite.SQLiteDatabase; import java.util.Iterator;
import android.app.Activity;
import android.database.Cursor;
import android.util.Log;
abstract public class AbstractController { abstract public class AbstractController {
protected Context context; protected Activity activity;
protected SQLiteDatabase database;
// special columns // special columns
public static final String KEY_ROWID = "_id"; 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 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<TYPE extends AbstractModel> implements Iterator<TYPE> {
Cursor cursor;
Class<TYPE> cls;
public CursorIterator(Cursor cursor, Class<TYPE> 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");
}
}
} }

@ -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();
}
}

@ -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());
}
}

@ -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<TagModelForView> getAllTags()
throws SQLException {
List<TagModelForView> list = new LinkedList<TagModelForView>();
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<TagIdentifier, TagModelForView> getAllTagsAsMap() throws SQLException {
Map<TagIdentifier, TagModelForView> map = new HashMap<TagIdentifier, TagModelForView>();
for(TagModelForView tag : getAllTags())
map.put(tag.getTagIdentifier(), tag);
return map;
}
/** Get a list of tag identifiers for the given task */
public List<TagIdentifier> getTaskTags(TaskIdentifier
taskId) throws SQLException {
List<TagIdentifier> list = new LinkedList<TagIdentifier>();
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<TaskIdentifier> getTaggedTasks(TagIdentifier
tagId) throws SQLException {
List<TaskIdentifier> list = new LinkedList<TaskIdentifier>();
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();
}
}

@ -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);
}
}

@ -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();
}
}

@ -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());
}
}

@ -40,8 +40,10 @@ public abstract class AbstractTaskModel extends AbstractModel {
static final String DEFINITE_DUE_DATE = "definiteDueDate"; static final String DEFINITE_DUE_DATE = "definiteDueDate";
static final String PREFERRED_DUE_DATE = "preferredDueDate"; static final String PREFERRED_DUE_DATE = "preferredDueDate";
static final String HIDDEN_UNTIL = "hiddenUntil"; static final String HIDDEN_UNTIL = "hiddenUntil";
// reserved fields
static final String BLOCKING_ON = "blockingOn"; static final String BLOCKING_ON = "blockingOn";
static final String NOTIFICATIONS = "notifications"; static final String NOTIFICATIONS = "notifications";
// end reserved fields
static final String CREATION_DATE = "creationDate"; static final String CREATION_DATE = "creationDate";
static final String COMPLETION_DATE = "completionDate"; static final String COMPLETION_DATE = "completionDate";
@ -75,10 +77,10 @@ public abstract class AbstractTaskModel extends AbstractModel {
static class TaskModelDatabaseHelper extends SQLiteOpenHelper { static class TaskModelDatabaseHelper extends SQLiteOpenHelper {
String tableName; String tableName;
TaskModelDatabaseHelper(Context context, String databaseName) { TaskModelDatabaseHelper(Context context, String databaseName, String tableName) {
super(context, databaseName, null, VERSION); super(context, databaseName, null, VERSION);
this.tableName = databaseName; this.tableName = tableName;
} }
@Override @Override

@ -1,10 +1,12 @@
package com.timsu.astrid.data.task; package com.timsu.astrid.data.task;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import android.content.Context; import android.app.Activity;
import android.database.Cursor; import android.database.Cursor;
import android.database.SQLException; import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import com.timsu.astrid.data.AbstractController; import com.timsu.astrid.data.AbstractController;
@ -12,6 +14,8 @@ import com.timsu.astrid.data.task.AbstractTaskModel.TaskModelDatabaseHelper;
public class TaskController extends AbstractController { public class TaskController extends AbstractController {
private SQLiteDatabase database;
// --- task list operations // --- task list operations
/** Return a list of all of the tasks with progress < COMPLETE_PERCENTAGE */ /** 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 */ /** Create a weighted list of tasks from the db cursor given */
public List<TaskModelForList> createTaskListFromCursor(Cursor cursor, public List<TaskModelForList> createTaskListFromCursor(Cursor cursor,
boolean hideHidden) { boolean hideHidden) {
return TaskModelForList.createTaskModelList(cursor, hideHidden); List<TaskModelForList> list = new ArrayList<TaskModelForList>();
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<TaskIdentifier> 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 // --- single task operations
@ -74,12 +109,13 @@ public class TaskController extends AbstractController {
} }
/** Returns a TaskModelForEdit corresponding to the given TaskIdentifier */ /** 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(); long id = taskId.getId();
Cursor cursor = database.query(true, TASK_TABLE_NAME, Cursor cursor = database.query(true, TASK_TABLE_NAME,
TaskModelForEdit.FIELD_LIST, TaskModelForEdit.FIELD_LIST,
KEY_ROWID + "=" + id, null, null, null, null, null); KEY_ROWID + "=" + id, null, null, null, null, null);
activity.startManagingCursor(cursor);
if (cursor != null) { if (cursor != null) {
cursor.moveToFirst(); cursor.moveToFirst();
TaskModelForEdit model = new TaskModelForEdit(taskId, cursor); TaskModelForEdit model = new TaskModelForEdit(taskId, cursor);
@ -97,7 +133,7 @@ public class TaskController extends AbstractController {
Cursor cursor = database.query(true, TASK_TABLE_NAME, Cursor cursor = database.query(true, TASK_TABLE_NAME,
TaskModelForView.FIELD_LIST, TaskModelForView.FIELD_LIST,
KEY_ROWID + "=" + id, null, null, null, null, null); KEY_ROWID + "=" + id, null, null, null, null, null);
activity.startManagingCursor(cursor);
if (cursor != null) { if (cursor != null) {
cursor.moveToFirst(); cursor.moveToFirst();
TaskModelForView model = new TaskModelForView(taskId, cursor); 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 * Constructor - takes the context to allow the database to be
* opened/created * opened/created
*/ */
public TaskController(Context context) { public TaskController(Activity activity) {
this.context = context; this.activity = activity;
} }
/** /**
@ -129,7 +165,7 @@ public class TaskController extends AbstractController {
*/ */
public TaskController open() throws SQLException { public TaskController open() throws SQLException {
SQLiteOpenHelper databaseHelper = new TaskModelDatabaseHelper( SQLiteOpenHelper databaseHelper = new TaskModelDatabaseHelper(
context, TASK_TABLE_NAME); activity, TASK_TABLE_NAME, TASK_TABLE_NAME);
database = databaseHelper.getWritableDatabase(); database = databaseHelper.getWritableDatabase();
return this; return this;
} }

@ -1,15 +1,11 @@
package com.timsu.astrid.data.task; 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) { public class TaskIdentifier extends Identifier {
this.id = id;
}
public long getId() { public TaskIdentifier(long id) {
return id; super(id);
} }
} }

@ -1,10 +1,10 @@
package com.timsu.astrid.data.task; package com.timsu.astrid.data.task;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import android.database.Cursor; import android.database.Cursor;
@ -30,25 +30,24 @@ public class TaskModelForList extends AbstractTaskModel {
HIDDEN_UNTIL, HIDDEN_UNTIL,
}; };
static List<TaskModelForList> createTaskModelList(Cursor cursor, /** Takes the incoming list of task models and weights it, removing hidden
boolean hideHidden) { * tasks if desired. This mutates the list */
ArrayList<TaskModelForList> list = new ArrayList<TaskModelForList>(); static List<TaskModelForList> sortAndFilterList(
List<TaskModelForList> list, boolean hideHidden) {
final HashMap<TaskModelForList, Integer> weights = new final HashMap<TaskModelForList, Integer> weights = new
HashMap<TaskModelForList, Integer>(); HashMap<TaskModelForList, Integer>();
// first, load everything // first, load everything
for(int i = 0; i < cursor.getCount(); i++) { for(Iterator<TaskModelForList> i = list.iterator(); i.hasNext(); ) {
cursor.moveToNext(); TaskModelForList task = i.next();
TaskModelForList task = new TaskModelForList(cursor);
// hide tasks
if(hideHidden) { if(hideHidden) {
if(task.getHiddenUntil() != null && if(task.getHiddenUntil() != null && task.getHiddenUntil().
task.getHiddenUntil().getTime() > System.currentTimeMillis()) getTime() > System.currentTimeMillis()) {
continue; i.remove();
continue;
}
} }
list.add(task);
weights.put(task, task.getWeight()); weights.put(task, task.getWeight());
} }

@ -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();
}
}

@ -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();
}
}
Loading…
Cancel
Save