Lots of changes:

- progress checkboxes
   - feature: completing a task queries you to stop the timer
   - feature: 'blocking on' dialog partially complete
   - feature: notification system...
   - feature: NNumberPicker
   - more informative edit screen
pull/14/head
Tim Su 16 years ago
parent b3d61b5884
commit 7c317b4b52

@ -3,5 +3,6 @@
<classpathentry kind="src" path="src"/>
<classpathentry kind="lib" path="lib/android-src.jar"/>
<classpathentry kind="lib" path="lib/android.jar" sourcepath="lib/android-src.jar"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="output" path="bin"/>
</classpath>

File diff suppressed because one or more lines are too long

@ -1,8 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.timsu.astrid"
android:versionCode="1"
android:versionName="1.3.0">
android:versionCode="6"
android:versionName="1.7">
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".activities.TaskList"
@ -19,5 +23,13 @@
<activity android:name=".activities.TagList"/>
</application>
<receiver android:name=".utilities.Notifications">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
</receiver>
</application>
</manifest>

@ -0,0 +1,65 @@
<?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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Enabled states -->
<item android:state_checked="true" android:state_window_focused="false"
android:state_enabled="true"
android:drawable="@drawable/btn_check_on" />
<item android:state_checked="false" android:state_window_focused="false"
android:state_enabled="true"
android:drawable="@drawable/btn_check_off" />
<item android:state_checked="true" android:state_pressed="true"
android:state_enabled="true"
android:drawable="@drawable/btn_check_on_pressed" />
<item android:state_checked="false" android:state_pressed="true"
android:state_enabled="true"
android:drawable="@drawable/btn_check_off_pressed" />
<item android:state_checked="true" android:state_focused="true"
android:state_enabled="true"
android:drawable="@drawable/btn_check_on_selected" />
<item android:state_checked="false" android:state_focused="true"
android:state_enabled="true"
android:drawable="@drawable/btn_check_off_selected" />
<item android:state_checked="false"
android:state_enabled="true"
android:drawable="@drawable/btn_check_off" />
<item android:state_checked="true"
android:state_enabled="true"
android:drawable="@drawable/btn_check_on" />
<!-- Disabled states -->
<item android:state_checked="true" android:state_window_focused="false"
android:drawable="@drawable/btn_check_on_disable" />
<item android:state_checked="false" android:state_window_focused="false"
android:drawable="@drawable/btn_check_off_disable" />
<item android:state_checked="true" android:state_focused="true"
android:drawable="@drawable/btn_check_on_disable_focused" />
<item android:state_checked="false" android:state_focused="true"
android:drawable="@drawable/btn_check_off_disable_focused" />
<item android:state_checked="false" android:drawable="@drawable/btn_check_off_disable" />
<item android:state_checked="true" android:drawable="@drawable/btn_check_on_disable" />
</selector>

@ -0,0 +1,35 @@
<?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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Enabled states -->
<item android:state_checked="true" android:state_pressed="true"
android:state_enabled="true"
android:drawable="@drawable/btn_check_on_pressed" />
<item android:state_checked="false" android:state_pressed="true"
android:state_enabled="true"
android:drawable="@drawable/btn_check_on_pressed" />
<item android:state_checked="false"
android:state_enabled="true"
android:drawable="@drawable/btn_check_25" />
<item android:state_checked="true"
android:state_enabled="true"
android:drawable="@drawable/btn_check_on" />
</selector>

@ -0,0 +1,35 @@
<?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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Enabled states -->
<item android:state_checked="true" android:state_pressed="true"
android:state_enabled="true"
android:drawable="@drawable/btn_check_on_pressed" />
<item android:state_checked="false" android:state_pressed="true"
android:state_enabled="true"
android:drawable="@drawable/btn_check_on_pressed" />
<item android:state_checked="false"
android:state_enabled="true"
android:drawable="@drawable/btn_check_50" />
<item android:state_checked="true"
android:state_enabled="true"
android:drawable="@drawable/btn_check_on" />
</selector>

@ -0,0 +1,35 @@
<?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
t
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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Enabled states -->
<item android:state_checked="true" android:state_pressed="true"
android:state_enabled="true"
android:drawable="@drawable/btn_check_on_pressed" />
<item android:state_checked="false" android:state_pressed="true"
android:state_enabled="true"
android:drawable="@drawable/btn_check_on_pressed" />
<item android:state_checked="false"
android:state_enabled="true"
android:drawable="@drawable/btn_check_75" />
<item android:state_checked="true"
android:state_enabled="true"
android:drawable="@drawable/btn_check_on" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 B

After

Width:  |  Height:  |  Size: 144 B

@ -2,8 +2,9 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:state_pressed="false"
android:drawable="@drawable/highlight_selected" />
<item android:state_focused="true" android:state_pressed="true"
android:drawable="@drawable/highlight_pressed" />
android:drawable="@drawable/transparent_button_transition" />
<item android:state_focused="false" android:state_pressed="true"
android:drawable="@drawable/highlight_pressed" />
</selector>
android:drawable="@drawable/transparent_button_transition" />
</selector>

@ -0,0 +1,21 @@
<?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.
-->
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/highlight_pressed" />
<item android:drawable="@drawable/highlight_longpress" />
</transition>

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
**
** Copyright 2007, 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:id="@+id/container"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="5dip">
</LinearLayout>

@ -49,7 +49,7 @@
android:capitalize="words"/>
</LinearLayout>
<!-- PROPERTIES -->
<!-- VITAL PROPERTIES -->
<View
android:layout_width="fill_parent"
android:layout_height="1dip"
@ -61,6 +61,16 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="5dip">
<TextView android:id="@+id/importance_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/importance_label"
style="@style/TextAppearance.EditEvent_Label"/>
<Spinner android:id="@+id/importance"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView android:id="@+id/estimatedDuration_label"
android:layout_width="wrap_content"
@ -71,16 +81,16 @@
<Button android:id="@+id/estimatedDuration"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView android:id="@+id/importance_label"
<TextView android:id="@+id/notification_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/importance_label"
android:text="@string/notification_label"
style="@style/TextAppearance.EditEvent_Label"/>
<Spinner android:id="@+id/importance"
<Button android:id="@+id/notification"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"/>
<TextView android:id="@+id/tags_label"
android:layout_width="wrap_content"
@ -157,6 +167,20 @@
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
<!-- HIDING -->
<View
android:layout_width="fill_parent"
android:layout_height="1dip"
android:background="@android:drawable/divider_horizontal_dark"
/>
<LinearLayout android:id="@+id/hiding_container"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="5dip">
<TextView android:id="@+id/hiddenUntil_label"
android:layout_width="wrap_content"
@ -182,10 +206,29 @@
android:layout_height="wrap_content"/>
</LinearLayout>
<TextView android:id="@+id/blockingon_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/importance_label"
style="@style/TextAppearance.EditEvent_Label"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<CheckBox android:id="@+id/blockingOn_notnull"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Spinner android:id="@+id/blockingon"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
<!-- TODO: blocking on -->
<!-- MISC FIELDS -->
<View
android:layout_width="fill_parent"
@ -193,7 +236,7 @@
android:background="@android:drawable/divider_horizontal_dark"
/>
<LinearLayout android:id="@+id/dates_container"
<LinearLayout android:id="@+id/misc_container"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"

@ -29,6 +29,7 @@
<CheckBox android:id="@+id/cb1"
android:gravity="center_vertical"
android:background="@drawable/btn_check0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="5dip"/>
@ -39,20 +40,45 @@
android:layout_height="fill_parent"
android:paddingLeft="5dip"/>
<TextView android:id="@+id/text1"
android:layout_height="?android:attr/listPreferredItemHeight"
android:layout_width="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
<LinearLayout android:id="@+id/text_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:paddingLeft="5dip"
android:singleLine="true"/>
<TextView android:id="@+id/text2"
android:layout_height="?android:attr/listPreferredItemHeight"
android:layout_width="fill_parent"
android:textAppearance="?android:attr/textAppearanceSmall"
android:gravity="center_vertical"
android:paddingLeft="15dip"
android:singleLine="true"/>
android:layout_height="fill_parent">
<TextView android:id="@+id/text1"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:textAppearance="?android:attr/textAppearanceMedium"
android:gravity="center_vertical"
android:singleLine="true"/>
<LinearLayout android:id="@+id/prop_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:id="@+id/text_dueDate"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@color/taskList_dueDate"
android:gravity="center_vertical"
android:paddingRight="15dip"
android:singleLine="true"/>
<TextView android:id="@+id/text_tags"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@color/taskList_tags"
android:gravity="center_vertical"
android:paddingRight="15dip"
android:singleLine="true"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:id="@+id/greeting"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView android:id="@+id/taskname"
android:paddingTop="20dip"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<Button android:id="@+id/btn_viewtask"
android:paddingTop="40dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/addtask_label"/>
<Button android:id="@+id/btn_tasklist"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/addtask_label"/>
</LinearLayout>

@ -66,7 +66,8 @@
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="5px"
android:paddingLeft="5px"
android:paddingTop="5px"
android:text="@string/taskView_elapsed"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView android:id="@+id/cell_elapsed"
@ -152,6 +153,31 @@
android:colorForeground="@color/view_table_values" />
</LinearLayout>
<!-- Creation Date -->
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="5px"
android:src="@android:drawable/divider_horizontal_bright" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="5px"
android:text="@string/taskView_creationDate"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView android:id="@+id/cell_creationDate"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="5px"
android:textAppearance="?android:attr/textAppearanceSmall"
android:colorForeground="@color/view_table_values" />
</LinearLayout>
<!-- Notes -->
<LinearLayout
android:orientation="vertical"

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 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.
-->
<resources>
<!-- Choices for the "Reminder minutes" spinner.
These must be kept in sync with the reminder_minutes_values array.
-->
<string-array name="reminders">
<item>Hi there! Have a second?</item>
<item>Can I see you for a moment?</item>
<item>Have a few minutes?</item>
<item>I think you forgot to do something...</item>
<item>Excuse me?</item>
<item>When you have a minute...</item>
<item>Look what I dug up!</item>
<item>You look energetic today!</item>
<item>Here's what I got for you...</item>
<item>Are you free for a moment?</item>
<item>Your friendly neighborhood assistant here!</item>
<item>I would like a minute of your time.</item>
<item>It's a beautiful day today, isn't it?</item>
</string-array>
<!-- Astrid says... (user should answer yes or no)-->
<string-array name="reminder_responses">
<item>I've got something for you!</item>
<item>Say hello to my little friend!</item>
<item>Why don't you get this done?</item>
<item>How about it? Ready tiger?</item>
<item>Ready to do this?</item>
<item>Can you handle this?</item>
<item>You can be happy! Just finish this!</item>
<item>Feel the rush of productivity!</item>
<item>Won't you do this today?</item>
<item>Please finish this, I'm sick of it!</item>
<item>Can you finish this? Yes you can!</item>
<item>Feel good about yourself! Let's go!</item>
<item>I'm so proud of you! Lets get it done!</item>
<item>A little snack after you finish this?</item>
<item>Just this one task? Please?</item>
<item>Time to shorten your todo list!</item>
</string-array>
</resources>

@ -23,6 +23,9 @@
<color name="task_list_overdue">#ffff4444</color>
<color name="task_list_done">#ffaaaaaa</color>
<color name="taskList_dueDate">#ffffff44</color>
<color name="taskList_tags">#ff888888</color>
<color name="view_header_done">#ff83ffa9</color>
<color name="view_table_values">#ffbbbbbb</color>
<color name="view_table_overdue">#ffff0000</color>

@ -46,10 +46,12 @@
<item quantity="one">1 Day</item>
<item quantity="other">%d Days</item>
</plurals>
<string name="days">D\na\ny\ns</string>
<plurals name="Nhours">
<item quantity="one">1 Hour</item>
<item quantity="other">%d Hours</item>
</plurals>
<string name="hours">H\no\nu\nr\ns</string>
<plurals name="Nminutes">
<item quantity="one">1 Minute</item>
<item quantity="other">%d Minutes</item>
@ -64,7 +66,10 @@
<string name="taskList_titlePrefix">Astrid: </string>
<string name="taskList_titleTagPrefix">Tasks Tagged \"%s\": </string>
<string name="taskList_hiddenSuffix"> hidden</string>
<string name="taskList_dueIn">Due in</string>
<string name="taskList_overdueBy">Overdue by</string>
<string name="addtask_label">New Task</string>
<string name="tags_suffix">Tags: </string>
<string name="taskList_menu_insert">Add</string>
<string name="taskList_menu_tags">Tags</string>
@ -75,6 +80,7 @@
<string name="taskList_context_delete">Delete Task</string>
<string name="taskList_context_startTimer">Start Timer</string>
<string name="taskList_context_stopTimer">Stop Timer</string>
<string name="taskList_filter_title">Filters</string>
<string name="taskList_filter_hidden">Hidden/Blocked Tasks</string>
<string name="taskList_filter_done">Completed Tasks</string>
@ -87,17 +93,20 @@
<string name="name_label">What</string>
<string name="name_hint">Task Description</string>
<string name="estimatedDuration_label">Estimated Time Needed</string>
<string name="estimatedDuration_label">How Long Will it Take?</string>
<string name="elapsedDuration_label">Time Already Spent on Task</string>
<string name="importance_label">How important is it?</string>
<string name="tags_label">Tags</string>
<string name="importance_label">How Important is it?</string>
<string name="tags_label">Tags:</string>
<string name="notification_label">Reminders</string>
<string name="notification_prefix">Every</string>
<string name="definiteDueDate_label">Absolute Deadline</string>
<string name="preferredDueDate_label">Goal Deadline</string>
<string name="hiddenUntil_label">Hide Until This Date</string>
<string name="blockingOn_label">Blocking On</string>
<string name="blockingOn_label">Hide Until This Task is Done</string>
<string name="notes_label">Notes</string>
<string name="notes_hint">Enter Task Notes</string>
<string name="minutes_dialog">Time (in minutes)</string>
<string name="hour_minutes_dialog">Time (hours : minutes)</string>
<string name="notification_dialog">Remind Me Every</string>
<string name="save_label">Save</string>
<string name="discard_label">Discard</string>
@ -110,6 +119,7 @@
<skip />
<string name="taskView_title">Astrid: Task Properties</string>
<string name="taskView_notifyTitle">Astrid says...</string>
<string name="startTimer_label">Start Timer</string>
<string name="stopTimer_label">Stop Timer</string>
<string name="progress_suffix">% Done</string>
@ -118,8 +128,11 @@
<string name="taskView_estimated">Estimated Time</string>
<string name="taskView_definiteDueDate">Absolute Deadline</string>
<string name="taskView_preferredDueDate">Goal Deadline</string>
<string name="taskView_creationDate">Creation Date</string>
<string name="taskView_tags">Tags</string>
<string name="taskView_notes">Task Notes</string>
<string name="overdue_suffix"> Overdue</string>
<string name="ago_suffix"> Ago</string>
<string name="progress_dialog">% of Task Finished</string>
<!-- TaskList -->
@ -129,10 +142,15 @@
<string name="tagList_context_edit">Edit Tag</string>
<string name="tagList_context_delete">Delete Tag</string>
<!-- Utilities -->
<!-- Dialog Boxes -->
<skip />
<string name="question_title">Question</string>
<string name="delete_title">Delete</string>
<string name="delete_this_task_title">Delete this task?</string>
<string name="delete_this_tag_title">Remove this tag from all tasks?</string>
<string name="stop_timer_title">Stop the timer?</string>
</resources>

@ -80,7 +80,7 @@ public class TagList extends Activity {
private void fillData() {
Resources r = getResources();
tagArray = controller.getAllTags();
tagArray = controller.getAllTags(this);
// set up the title
StringBuilder title = new StringBuilder().
@ -163,7 +163,7 @@ public class TagList extends Activity {
// set up basic properties
view.setTag(tag);
List<TaskIdentifier> tasks = controller.getTaggedTasks(
List<TaskIdentifier> tasks = controller.getTaggedTasks(TagList.this,
tag.getTagIdentifier());
final TextView name = ((TextView)view.findViewById(android.R.id.text1));

@ -30,6 +30,7 @@ import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.database.Cursor;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
@ -42,11 +43,15 @@ 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.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.SimpleCursorAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.CompoundButton.OnCheckedChangeListener;
import com.timsu.astrid.R;
import com.timsu.astrid.data.enums.Importance;
@ -55,8 +60,11 @@ import com.timsu.astrid.data.tag.TagIdentifier;
import com.timsu.astrid.data.tag.TagModelForView;
import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.data.task.TaskModelForEdit;
import com.timsu.astrid.data.task.TaskModelForList;
import com.timsu.astrid.utilities.Notifications;
import com.timsu.astrid.widget.DateControlSet;
import com.timsu.astrid.widget.TimeDurationControlSet;
import com.timsu.astrid.widget.TimeDurationControlSet.TimeDurationType;
public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> {
@ -79,9 +87,11 @@ public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> {
private Spinner importance;
private TimeDurationControlSet estimatedDuration;
private TimeDurationControlSet elapsedDuration;
private TimeDurationControlSet notification;
private DateControlSet definiteDueDate;
private DateControlSet preferredDueDate;
private DateControlSet hiddenUntil;
private BlockingOnControlSet blockingOn;
private EditText notes;
private LinearLayout tagsContainer;
@ -109,7 +119,7 @@ public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> {
@Override
protected TaskModelForEdit getModel(TaskIdentifier identifier) {
if (identifier != null)
return controller.fetchTaskForEdit(identifier);
return controller.fetchTaskForEdit(this, identifier);
else
return controller.createNewTaskForEdit();
}
@ -130,18 +140,20 @@ public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> {
append(r.getString(R.string.taskEdit_titlePrefix)).
append(" ").
append(model.getName()));
estimatedDuration.setTimeElapsed(model.getEstimatedSeconds());
elapsedDuration.setTimeElapsed(model.getElapsedSeconds());
estimatedDuration.setTimeDuration(model.getEstimatedSeconds());
elapsedDuration.setTimeDuration(model.getElapsedSeconds());
importance.setSelection(model.getImportance().ordinal());
definiteDueDate.setDate(model.getDefiniteDueDate());
preferredDueDate.setDate(model.getPreferredDueDate());
hiddenUntil.setDate(model.getHiddenUntil());
blockingOn.setBlockingOn(model.getBlockingOn());
notification.setTimeDuration(model.getNotificationIntervalSeconds());
notes.setText(model.getNotes());
// tags
tags = tagController.getAllTags();
tags = tagController.getAllTags(this);
if(model.getTaskIdentifier() != null) {
taskTags = tagController.getTaskTags(model.getTaskIdentifier());
taskTags = tagController.getTaskTags(this, model.getTaskIdentifier());
if(taskTags.size() > 0) {
Map<TagIdentifier, TagModelForView> tagsMap =
new HashMap<TagIdentifier, TagModelForView>();
@ -174,7 +186,9 @@ public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> {
model.setDefiniteDueDate(definiteDueDate.getDate());
model.setPreferredDueDate(preferredDueDate.getDate());
model.setHiddenUntil(hiddenUntil.getDate());
model.setBlockingOn(blockingOn.getBlockingOn());
model.setNotes(notes.getText().toString());
model.setNotificationIntervalSeconds(notification.getTimeDurationInSeconds());
try {
// write out to database
@ -184,6 +198,10 @@ public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> {
Log.e(getClass().getSimpleName(), "Error saving task!", e); // TODO
}
// recompute task visibility
// set up notification
Notifications.scheduleNextNotification(this, model);
}
/** Save task tags. Must be called after task already has an ID */
@ -238,8 +256,15 @@ public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> {
name = (EditText)findViewById(R.id.name);
importance = (Spinner)findViewById(R.id.importance);
tagsContainer = (LinearLayout)findViewById(R.id.tags_container);
estimatedDuration = new TimeDurationControlSet(this, R.id.estimatedDuration);
elapsedDuration = new TimeDurationControlSet(this, R.id.elapsedDuration);
estimatedDuration = new TimeDurationControlSet(this,
R.id.estimatedDuration, 0, R.string.hour_minutes_dialog,
TimeDurationType.HOURS_MINUTES);
elapsedDuration = new TimeDurationControlSet(this, R.id.elapsedDuration,
0, R.string.hour_minutes_dialog,
TimeDurationType.HOURS_MINUTES);
notification = new TimeDurationControlSet(this, R.id.notification,
R.string.notification_prefix, R.string.notification_dialog,
TimeDurationType.DAYS_HOURS);
definiteDueDate = new DateControlSet(this, R.id.definiteDueDate_notnull,
R.id.definiteDueDate_date, R.id.definiteDueDate_time);
preferredDueDate = new DateControlSet(this, R.id.preferredDueDate_notnull,
@ -247,6 +272,8 @@ public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> {
hiddenUntil = new DateControlSet(this, R.id.hiddenUntil_notnull,
R.id.hiddenUntil_date, R.id.hiddenUntil_time);
notes = (EditText)findViewById(R.id.notes);
blockingOn = new BlockingOnControlSet(R.id.blockingOn_notnull,
R.id.blockingon);
// individual ui component initialization
ImportanceAdapter importanceAdapter = new ImportanceAdapter(this,
@ -488,4 +515,56 @@ public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> {
return view;
}
}
public class BlockingOnControlSet {
private CheckBox activatedCheckBox;
private Spinner taskBox;
public BlockingOnControlSet(int checkBoxId, int taskBoxId) {
activatedCheckBox = (CheckBox)findViewById(checkBoxId);
taskBox = (Spinner)findViewById(taskBoxId);
Cursor tasks = controller.getActiveTaskListCursor();
startManagingCursor(tasks);
SimpleCursorAdapter tasksAdapter = new SimpleCursorAdapter(TaskEdit.this,
android.R.layout.simple_list_item_1, tasks,
new String[] { TaskModelForList.getNameField() },
new int[] { android.R.id.text1 });
taskBox.setAdapter(tasksAdapter);
activatedCheckBox.setOnCheckedChangeListener(
new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
taskBox.setEnabled(isChecked);
}
});
}
public void setBlockingOn(TaskIdentifier value) {
activatedCheckBox.setChecked(value != null);
if(value == null) {
return;
}
for(int i = 0; i < taskBox.getCount(); i++)
if(taskBox.getItemIdAtPosition(i) == value.getId()) {
taskBox.setSelection(i);
return;
}
// not found
activatedCheckBox.setChecked(false);
}
public TaskIdentifier getBlockingOn() {
if(!activatedCheckBox.isChecked())
return null;
return new TaskIdentifier(taskBox.getSelectedItemId());
}
}
}

@ -19,38 +19,29 @@
*/
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;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
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.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.CompoundButton.OnCheckedChangeListener;
import com.timsu.astrid.R;
import com.timsu.astrid.activities.TaskListAdapter.TaskListAdapterHooks;
import com.timsu.astrid.data.tag.TagController;
import com.timsu.astrid.data.tag.TagIdentifier;
import com.timsu.astrid.data.tag.TagModelForView;
@ -76,17 +67,14 @@ public class TaskList extends Activity {
private static final int ACTIVITY_EDIT = 2;
private static final int ACTIVITY_TAGS = 3;
// menu ids
// menu codes
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
// UI components
private TaskController controller;
private TagController tagController = null;
private ListView listView;
@ -148,7 +136,7 @@ public class TaskList extends Activity {
Cursor tasksCursor;
// load tags (again)
tagMap = tagController.getAllTagsAsMap();
tagMap = tagController.getAllTagsAsMap(this);
Bundle extras = getIntent().getExtras();
if(extras != null && extras.containsKey(TAG_TOKEN)) {
TagIdentifier identifier = new TagIdentifier(extras.getLong(TAG_TOKEN));
@ -157,7 +145,7 @@ public class TaskList extends Activity {
// get the array of tasks
if(filterTag != null) {
List<TaskIdentifier> tasks = tagController.getTaggedTasks(
List<TaskIdentifier> tasks = tagController.getTaggedTasks(this,
filterTag.getTagIdentifier());
tasksCursor = controller.getTaskListCursorById(tasks);
@ -192,8 +180,33 @@ public class TaskList extends Activity {
setTitle(title);
// set up our adapter
TaskListAdapter tasks = new TaskListAdapter(this,
R.layout.task_list_row, taskArray);
TaskListAdapter tasks = new TaskListAdapter(this, this,
R.layout.task_list_row, taskArray, new TaskListAdapterHooks() {
@Override
public TagController getTagController() {
return tagController;
}
@Override
public Map<TagIdentifier, TagModelForView> getTagMap() {
return tagMap;
}
@Override
public List<TaskModelForList> getTaskArray() {
return taskArray;
}
@Override
public TaskController getTaskController() {
return controller;
}
@Override
public void performItemClick(View v, int position) {
listView.performItemClick(v, position, 0);
}
});
listView.setAdapter(tasks);
listView.setItemsCanFocus(true);
@ -220,139 +233,6 @@ public class TaskList extends Activity {
// --- list adapter
private class TaskListAdapter extends ArrayAdapter<TaskModelForList> {
private List<TaskModelForList> objects;
private int resource;
private LayoutInflater inflater;
public TaskListAdapter(Context context, int resource,
List<TaskModelForList> 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));
addListeners(position, view);
return view;
}
public void setupView(View view, final TaskModelForList task) {
Resources r = getResources();
// 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));
view.setTag(task);
progress.setTag(task);
name.setText(task.getName());
if(task.getTimerStart() != null)
timer.setImageDrawable(r.getDrawable(R.drawable.ic_dialog_time));
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);
}
public void addListeners(final int position, final View view) {
final CheckBox progress = ((CheckBox)view.findViewById(R.id.cb1));
final TextView name = ((TextView)view.findViewById(R.id.text1));
// clicking the check box
progress.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
TaskModelForList task = (TaskModelForList)buttonView.getTag();
int newProgressPercentage;
if(isChecked)
newProgressPercentage = TaskModelForList.getCompletedPercentage();
else
newProgressPercentage = 0;
// this takes care of initial checked change
if(newProgressPercentage != task.getProgressPercentage()) {
task.setProgressPercentage(newProgressPercentage);
controller.saveTask(task);
setTaskAppearance(name, task);
}
}
});
// interacting with the text field
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listView.performItemClick(view, position, 0);
}
});
view.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
TaskModelForList task = (TaskModelForList)v.getTag();
menu.add(position, CONTEXT_EDIT_ID, Menu.NONE,
R.string.taskList_context_edit);
menu.add(position, CONTEXT_DELETE_ID, Menu.NONE,
R.string.taskList_context_delete);
int timerTitle;
if(task.getTimerStart() == null)
timerTitle = R.string.taskList_context_startTimer;
else
timerTitle = R.string.taskList_context_stopTimer;
menu.add(position, CONTEXT_TIMER_ID, Menu.NONE, timerTitle);
menu.setHeaderTitle(task.getName());
}
});
}
public void setTaskAppearance(TextView name, TaskModelForList task) {
Resources r = getResources();
if(task.isTaskCompleted()) {
name.setBackgroundDrawable(r.getDrawable(R.drawable.strikeout));
name.setTextColor(r.getColor(R.color.task_list_done));
} else {
name.setBackgroundDrawable(null);
name.setTextColor(r.getColor(task.getTaskColorResource()));
}
}
}
// --- ui control handlers
private void createTask() {
Intent intent = new Intent(this, TaskEdit.class);
if(filterTag != null)
@ -398,17 +278,17 @@ public class TaskList extends Activity {
}
return true;
case CONTEXT_EDIT_ID:
case TaskListAdapter.CONTEXT_EDIT_ID:
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;
case CONTEXT_DELETE_ID:
case TaskListAdapter.CONTEXT_DELETE_ID:
task = taskArray.get(item.getGroupId());
deleteTask(task.getTaskIdentifier());
return true;
case CONTEXT_TIMER_ID:
case TaskListAdapter.CONTEXT_TIMER_ID:
task = taskArray.get(item.getGroupId());
if(task.getTimerStart() == null)
task.setTimerStart(new Date());

@ -0,0 +1,269 @@
/*
* ASTRID: Android's Simple Task Recording Dashboard
*
* 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.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnCreateContextMenuListener;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
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.TaskModelForList;
import com.timsu.astrid.utilities.DateUtilities;
/** Adapter for displaying a list of TaskModelForList entities
*
* @author timsu
*
*/
public class TaskListAdapter extends ArrayAdapter<TaskModelForList> {
public static final int CONTEXT_EDIT_ID = Menu.FIRST + 50;
public static final int CONTEXT_DELETE_ID = Menu.FIRST + 51;
public static final int CONTEXT_TIMER_ID = Menu.FIRST + 52;
private final Activity activity;
private List<TaskModelForList> objects;
private int resource;
private LayoutInflater inflater;
private TaskListAdapterHooks hooks;
public interface TaskListAdapterHooks {
List<TaskModelForList> getTaskArray();
Map<TagIdentifier, TagModelForView> getTagMap();
TaskController getTaskController();
TagController getTagController();
void performItemClick(View v, int position);
}
public TaskListAdapter(Activity activity, Context context, int resource,
List<TaskModelForList> objects, TaskListAdapterHooks hooks) {
super(context, resource, objects);
inflater = (LayoutInflater)context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
this.objects = objects;
this.resource = resource;
this.activity = activity;
this.hooks = hooks;
}
// --- code for setting up each view
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
view = inflater.inflate(resource, parent, false);
setupView(view, objects.get(position));
addListeners(position, view);
return view;
}
private void setupView(View view, final TaskModelForList task) {
Resources r = activity.getResources();
// find UI components
final TextView name = ((TextView)view.findViewById(R.id.text1));
final TextView dueDateView = ((TextView)view.findViewById(R.id.text_dueDate));
final TextView tagsView = ((TextView)view.findViewById(R.id.text_tags));
final CheckBox progress = ((CheckBox)view.findViewById(R.id.cb1));
final ImageView timer = ((ImageView)view.findViewById(R.id.image1));
final LinearLayout properties = (LinearLayout)view.findViewById(R.id.prop_layout);
view.setTag(task);
progress.setTag(task);
name.setText(task.getName());
if(task.getTimerStart() != null)
timer.setImageDrawable(r.getDrawable(R.drawable.ic_dialog_time));
progress.setChecked(task.isTaskCompleted());
// due date
boolean hasProperties = false;
Date dueDate = task.getDefiniteDueDate();
if(dueDate == null || (task.getPreferredDueDate() != null &&
task.getPreferredDueDate().getTime() < dueDate.getTime()))
dueDate = task.getPreferredDueDate();
if(dueDate != null) {
int secondsLeft = (int)(dueDate.getTime() -
System.currentTimeMillis()) / 1000;
String dueString = " " + DateUtilities.getDurationString(r,
Math.abs(secondsLeft), 1);
if(secondsLeft > 0)
dueString = r.getString(R.string.taskList_dueIn) + dueString;
else
dueString = r.getString(R.string.taskList_overdueBy) + dueString;
dueDateView.setText(dueString);
hasProperties = true;
} else
dueDateView.setVisibility(View.GONE);
// tags
List<TagIdentifier> tags = hooks.getTagController().getTaskTags(
activity, task.getTaskIdentifier());
StringBuilder tagString = new StringBuilder();
for(Iterator<TagIdentifier> i = tags.iterator(); i.hasNext(); ) {
TagModelForView tag = hooks.getTagMap().get(i.next());
tagString.append(tag.getName());
if(i.hasNext())
tagString.append(", ");
}
if(tagString.length() > 0) {
tagsView.setText(r.getString(R.string.tags_suffix) + tagString);
hasProperties = true;
}
if(!hasProperties) { // no properties, old school view
properties.setVisibility(View.GONE);
name.setTextSize(36);
name.setBackgroundColor(r.getColor(R.color.importance_2));
}
setTaskAppearance(task, name, progress);
}
private void addListeners(final int position, final View view) {
final CheckBox progress = ((CheckBox)view.findViewById(R.id.cb1));
final TextView name = ((TextView)view.findViewById(R.id.text1));
// clicking the check box
progress.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
TaskModelForList task = (TaskModelForList)buttonView.getTag();
int newProgressPercentage;
if(isChecked)
newProgressPercentage =
TaskModelForList.getCompletedPercentage();
else
newProgressPercentage = 0;
if(newProgressPercentage != task.getProgressPercentage()) {
setTaskProgress(task, view, newProgressPercentage);
setTaskAppearance(task, name, progress);
}
}
});
// clicking the text field
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
hooks.performItemClick(view, position);
}
});
// long-clicking the text field
view.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
TaskModelForList task = (TaskModelForList)v.getTag();
menu.add(position, CONTEXT_EDIT_ID, Menu.NONE,
R.string.taskList_context_edit);
menu.add(position, CONTEXT_DELETE_ID, Menu.NONE,
R.string.taskList_context_delete);
int timerTitle;
if(task.getTimerStart() == null)
timerTitle = R.string.taskList_context_startTimer;
else
timerTitle = R.string.taskList_context_stopTimer;
menu.add(position, CONTEXT_TIMER_ID, Menu.NONE, timerTitle);
menu.setHeaderTitle(task.getName());
}
});
}
private void setTaskProgress(final TaskModelForList task, View view, int progress) {
final ImageView timer = ((ImageView)view.findViewById(R.id.image1));
task.setProgressPercentage(progress);
hooks.getTaskController().saveTask(task);
// if our timer is on, ask if we want to stop
if(task.getTimerStart() != null) {
new AlertDialog.Builder(activity)
.setTitle(R.string.question_title)
.setMessage(R.string.stop_timer_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
task.stopTimerAndUpdateElapsedTime();
hooks.getTaskController().saveTask(task);
timer.setVisibility(View.GONE);
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
}
private void setTaskAppearance(TaskModelForList task, TextView name, CheckBox progress) {
Resources r = activity.getResources();
if(task.isTaskCompleted()) {
name.setBackgroundDrawable(r.getDrawable(R.drawable.strikeout));
name.setTextColor(r.getColor(R.color.task_list_done));
} else {
name.setBackgroundDrawable(null);
name.setTextColor(r.getColor(task.getTaskColorResource()));
if(task.getProgressPercentage() >= 75)
progress.setButtonDrawable(R.drawable.btn_check75);
else if(task.getProgressPercentage() >= 50)
progress.setButtonDrawable(R.drawable.btn_check50);
else if(task.getProgressPercentage() >= 25)
progress.setButtonDrawable(R.drawable.btn_check25);
else
progress.setButtonDrawable(R.drawable.btn_check0);
}
}
}

@ -19,6 +19,7 @@
*/
package com.timsu.astrid.activities;
import java.util.Date;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
@ -38,6 +39,7 @@ import com.timsu.astrid.R;
import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.data.task.TaskModelForView;
import com.timsu.astrid.utilities.DateUtilities;
import com.timsu.astrid.utilities.Notifications;
import com.timsu.astrid.widget.NumberPicker;
import com.timsu.astrid.widget.NumberPickerDialog;
@ -48,24 +50,32 @@ import com.timsu.astrid.widget.NumberPickerDialog;
*
*/
public class TaskView extends TaskModificationActivity<TaskModelForView> {
private static final int ACTIVITY_EDIT = 0;
private static final int EDIT_ID = Menu.FIRST;
private static final int DELETE_ID = Menu.FIRST + 1;
private TextView name;
private TextView elapsed;
private TextView estimated;
private TextView definiteDueDate;
private TextView preferredDueDate;
private TextView notes;
private Button timerButton;
private Button progress;
// bundle tokens
public static final String FROM_NOTIFICATION_TOKEN = "notify";
// activities
private static final int ACTIVITY_EDIT = 0;
// menu codes
private static final int EDIT_ID = Menu.FIRST;
private static final int DELETE_ID = Menu.FIRST + 1;
// UI components
private TextView name;
private TextView elapsed;
private TextView estimated;
private TextView definiteDueDate;
private TextView preferredDueDate;
private TextView creationDate;
private TextView notes;
private Button timerButton;
private Button progress;
private NumberPickerDialog progressDialog;
private Handler handler;
private Timer updateTimer = null;
// other instance variables
private Handler handler;
private Timer updateTimer = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -76,16 +86,103 @@ public class TaskView extends TaskModificationActivity<TaskModelForView> {
setUpUIComponents();
setUpListeners();
populateFields();
Bundle extras = getIntent().getExtras();
if(extras != null && extras.containsKey(FROM_NOTIFICATION_TOKEN))
showNotificationAlert();
}
@Override
protected TaskModelForView getModel(TaskIdentifier identifier) {
if(identifier == null)
throw new IllegalArgumentException("Can't view null task!");
return controller.fetchTaskForView(identifier);
return controller.fetchTaskForView(this, identifier);
}
/** Called when user clicks on a notification to get here */
private void showNotificationAlert() {
Resources r = getResources();
// clear residual, schedule the next one
Notifications.clearAllNotifications(this, model.getTaskIdentifier());
Notifications.scheduleNextNotification(this, model);
String[] responses = r.getStringArray(R.array.reminder_responses);
String response = responses[new Random().nextInt(responses.length)];
new AlertDialog.Builder(this)
.setTitle(R.string.taskView_notifyTitle)
.setMessage(response)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(android.R.string.yes, null)
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
setResult(RESULT_CANCELED);
finish();
}
})
.show();
}
/* ======================================================================
* =================================================== reading from model
* ====================================================================== */
private void populateFields() {
final Resources r = getResources();
name.setText(model.getName());
estimated.setText(DateUtilities.getDurationString(r,
model.getEstimatedSeconds(), 2));
elapsed.setText(DateUtilities.getDurationString(r,
model.getElapsedSeconds(), 2));
formatDeadline(model.getDefiniteDueDate(), definiteDueDate);
formatDeadline(model.getPreferredDueDate(), preferredDueDate);
formatDate(model.getCreationDate(), creationDate);
updateTimerButtonText();
updateProgressComponents();
if(model.getNotes().length() == 0)
((View)notes.getParent()).setVisibility(View.GONE);
else
notes.setText(model.getNotes());
}
private void formatDeadline(Date deadline, TextView view) {
Resources r = getResources();
if(deadline == null || model.isTaskCompleted()) {
((View)view.getParent()).setVisibility(View.GONE);
return;
}
int secondsToDeadline = (int) ((deadline.getTime() -
System.currentTimeMillis())/1000);
String text = DateUtilities.getDurationString(r,
Math.abs(secondsToDeadline), 2);
if(secondsToDeadline < 0) {
view.setTextColor(r.getColor(R.color.view_table_overdue));
view.setText(text + r.getString(R.string.overdue_suffix));
} else
view.setText(text);
}
// --- initialization
private void formatDate(Date date, TextView view) {
Resources r = getResources();
if(date == null || model.isTaskCompleted()) {
((View)view.getParent()).setVisibility(View.GONE);
return;
}
int secondsAgo = (int) ((System.currentTimeMillis() - date.getTime())/1000);
String text = DateUtilities.getDurationString(r,
Math.abs(secondsAgo), 2);
view.setText(text + r.getString(R.string.ago_suffix));
}
/* ======================================================================
* ==================================================== UI initialization
* ====================================================================== */
private void setUpUIComponents() {
Resources r = getResources();
@ -96,6 +193,7 @@ public class TaskView extends TaskModificationActivity<TaskModelForView> {
estimated = (TextView)findViewById(R.id.cell_estimated);
definiteDueDate = (TextView)findViewById(R.id.cell_definiteDueDate);
preferredDueDate = (TextView)findViewById(R.id.cell_preferredDueDate);
creationDate = (TextView)findViewById(R.id.cell_creationDate);
notes = (TextView)findViewById(R.id.cell_notes);
timerButton = (Button)findViewById(R.id.timerButton);
progress = (Button)findViewById(R.id.progress);
@ -108,7 +206,7 @@ public class TaskView extends TaskModificationActivity<TaskModelForView> {
controller.saveTask(model);
updateProgressComponents();
}
}, r.getString(R.string.progress_dialog), 0, 10, 0, 100);
}, r.getString(R.string.progress_dialog), 0, 25, 0, 100);
name.setTextSize(36);
}
@ -144,44 +242,6 @@ public class TaskView extends TaskModificationActivity<TaskModelForView> {
});
}
private void populateFields() {
final Resources r = getResources();
name.setText(model.getName());
estimated.setText(DateUtilities.getDurationString(r,
model.getEstimatedSeconds(), 2));
elapsed.setText(DateUtilities.getDurationString(r,
model.getElapsedSeconds(), 2));
formatDeadline(model.getDefiniteDueDate(), definiteDueDate);
formatDeadline(model.getPreferredDueDate(), preferredDueDate);
updateTimerButtonText();
updateProgressComponents();
if(model.getNotes().length() == 0)
((View)notes.getParent()).setVisibility(View.GONE);
else
notes.setText(model.getNotes());
}
private void formatDeadline(Date deadline, TextView view) {
Resources r = getResources();
if(deadline == null || model.isTaskCompleted()) {
((View)view.getParent()).setVisibility(View.GONE);
return;
}
int secondsToDeadline = (int) ((deadline.getTime() -
System.currentTimeMillis())/1000);
String text = DateUtilities.getDurationString(r,
Math.abs(secondsToDeadline), 2);
if(secondsToDeadline < 0) {
view.setTextColor(r.getColor(R.color.view_table_overdue));
view.setText(text + r.getString(R.string.overdue_suffix));
} else
view.setText(text);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
@ -198,7 +258,9 @@ public class TaskView extends TaskModificationActivity<TaskModelForView> {
return true;
}
// --- event handlers
/* ======================================================================
* ======================================================= event handlers
* ====================================================================== */
@Override
protected void onActivityResult(int requestCode, int resultCode,
@ -242,8 +304,6 @@ public class TaskView extends TaskModificationActivity<TaskModelForView> {
}, 0, 1000);
}
// --- event response methods
private void editButtonClick() {
Intent intent = new Intent(TaskView.this, TaskEdit.class);
intent.putExtra(TaskEdit.LOAD_INSTANCE_TOKEN,
@ -283,6 +343,10 @@ public class TaskView extends TaskModificationActivity<TaskModelForView> {
return super.onMenuItemSelected(featureId, item);
}
/* ======================================================================
* ================================================ UI component updating
* ====================================================================== */
/** Update components that depend on elapsed time */
private void updateElapsedTimeText() {
Resources r = getResources();

@ -22,14 +22,14 @@ package com.timsu.astrid.data;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
/** Abstract controller class. Mostly contains some static fields */
abstract public class AbstractController {
protected Activity activity;
protected Context context;
// special columns
public static final String KEY_ROWID = "_id";

@ -43,7 +43,7 @@ public class TagController extends AbstractController {
// --- tag batch operations
/** Get a list of all tags */
public List<TagModelForView> getAllTags()
public List<TagModelForView> getAllTags(Activity activity)
throws SQLException {
List<TagModelForView> list = new LinkedList<TagModelForView>();
Cursor cursor = tagDatabase.query(TAG_TABLE_NAME,
@ -63,15 +63,15 @@ public class TagController extends AbstractController {
// --- tag to task map batch operations
/** Get a list of all tags as an id => tag map */
public Map<TagIdentifier, TagModelForView> getAllTagsAsMap() throws SQLException {
public Map<TagIdentifier, TagModelForView> getAllTagsAsMap(Activity activity) throws SQLException {
Map<TagIdentifier, TagModelForView> map = new HashMap<TagIdentifier, TagModelForView>();
for(TagModelForView tag : getAllTags())
for(TagModelForView tag : getAllTags(activity))
map.put(tag.getTagIdentifier(), tag);
return map;
}
/** Get a list of tag identifiers for the given task */
public List<TagIdentifier> getTaskTags(TaskIdentifier
public List<TagIdentifier> getTaskTags(Activity activity, TaskIdentifier
taskId) throws SQLException {
List<TagIdentifier> list = new LinkedList<TagIdentifier>();
Cursor cursor = tagToTaskMapDatabase.query(TAG_TASK_MAP_NAME,
@ -90,7 +90,7 @@ public class TagController extends AbstractController {
}
/** Get a list of task identifiers for the given tag */
public List<TaskIdentifier> getTaggedTasks(TagIdentifier
public List<TaskIdentifier> getTaggedTasks(Activity activity, TagIdentifier
tagId) throws SQLException {
List<TaskIdentifier> list = new LinkedList<TaskIdentifier>();
Cursor cursor = tagToTaskMapDatabase.query(TAG_TASK_MAP_NAME,
@ -194,7 +194,7 @@ public class TagController extends AbstractController {
* opened/created
*/
public TagController(Activity activity) {
this.activity = activity;
this.context = activity;
}
/**
@ -207,9 +207,9 @@ public class TagController extends AbstractController {
* @throws SQLException if the database could be neither opened or created
*/
public TagController open() throws SQLException {
tagToTaskMapDatabase = new TagToTaskMappingDatabaseHelper(activity,
tagToTaskMapDatabase = new TagToTaskMappingDatabaseHelper(context,
TAG_TASK_MAP_NAME, TAG_TASK_MAP_NAME).getWritableDatabase();
tagDatabase = new TagModelDatabaseHelper(activity,
tagDatabase = new TagModelDatabaseHelper(context,
TAG_TABLE_NAME, TAG_TABLE_NAME).getWritableDatabase();
return this;
}

@ -259,6 +259,10 @@ public abstract class AbstractTaskModel extends AbstractModel {
return new TaskIdentifier(value);
}
protected Integer getNotificationIntervalSeconds() {
return retrieveInteger(NOTIFICATIONS);
}
// --- setters
protected void setName(String name) {
@ -306,7 +310,7 @@ public abstract class AbstractTaskModel extends AbstractModel {
}
protected void setBlockingOn(TaskIdentifier blockingOn) {
if(blockingOn == null)
if(blockingOn == null || blockingOn.equals(getTaskIdentifier()))
setValues.put(BLOCKING_ON, (Integer)null);
else
setValues.put(BLOCKING_ON, blockingOn.getId());
@ -320,6 +324,10 @@ public abstract class AbstractTaskModel extends AbstractModel {
putDate(setValues, COMPLETION_DATE, completionDate);
}
protected void setNotificationIntervalSeconds(Integer intervalInSeconds) {
setValues.put(NOTIFICATIONS, intervalInSeconds);
}
// --- utility methods
static void putDate(ContentValues cv, String fieldName, Date date) {

@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
@ -38,6 +39,27 @@ public class TaskController extends AbstractController {
// --- task list operations
/** Return a list of all active tasks with notifications */
public List<TaskModelForNotify> getTasksWithNotifications() {
List<TaskModelForNotify> list = new ArrayList<TaskModelForNotify>();
Cursor cursor = database.query(TASK_TABLE_NAME, TaskModelForNotify.FIELD_LIST,
String.format("%s < %d AND %s != 0",
AbstractTaskModel.PROGRESS_PERCENTAGE,
AbstractTaskModel.COMPLETE_PERCENTAGE,
AbstractTaskModel.NOTIFICATIONS), null, null, null, null, null);
if(cursor.getCount() == 0)
return list;
do {
cursor.moveToNext();
list.add(new TaskModelForNotify(cursor));
} while(!cursor.isLast());
cursor.close();
return list;
}
/** Return a list of all of the tasks with progress < COMPLETE_PERCENTAGE */
public Cursor getActiveTaskListCursor() {
return database.query(TASK_TABLE_NAME, TaskModelForList.FIELD_LIST,
@ -129,7 +151,7 @@ public class TaskController extends AbstractController {
}
/** Returns a TaskModelForEdit corresponding to the given TaskIdentifier */
public TaskModelForEdit fetchTaskForEdit(TaskIdentifier
public TaskModelForEdit fetchTaskForEdit(Activity activity, TaskIdentifier
taskId) throws SQLException {
long id = taskId.getId();
Cursor cursor = database.query(true, TASK_TABLE_NAME,
@ -148,7 +170,8 @@ public class TaskController extends AbstractController {
/** Returns a TaskModelForView corresponding to the given TaskIdentifier */
public TaskModelForView fetchTaskForView(TaskIdentifier taskId) throws SQLException {
public TaskModelForView fetchTaskForView(Activity activity,
TaskIdentifier taskId) throws SQLException {
long id = taskId.getId();
Cursor cursor = database.query(true, TASK_TABLE_NAME,
TaskModelForView.FIELD_LIST,
@ -170,8 +193,8 @@ public class TaskController extends AbstractController {
* Constructor - takes the context to allow the database to be
* opened/created
*/
public TaskController(Activity activity) {
this.activity = activity;
public TaskController(Context activity) {
this.context = activity;
}
/**
@ -185,7 +208,7 @@ public class TaskController extends AbstractController {
*/
public TaskController open() throws SQLException {
SQLiteOpenHelper databaseHelper = new TaskModelDatabaseHelper(
activity, TASK_TABLE_NAME, TASK_TABLE_NAME);
context, TASK_TABLE_NAME, TASK_TABLE_NAME);
database = databaseHelper.getWritableDatabase();
return this;
}

@ -24,11 +24,12 @@ import java.util.Date;
import android.database.Cursor;
import com.timsu.astrid.data.enums.Importance;
import com.timsu.astrid.utilities.Notifications.Notifiable;
/** Fields that you would want to edit in the TaskModel */
public class TaskModelForEdit extends AbstractTaskModel {
public class TaskModelForEdit extends AbstractTaskModel implements Notifiable {
static String[] FIELD_LIST = new String[] {
NAME,
@ -39,6 +40,7 @@ public class TaskModelForEdit extends AbstractTaskModel {
PREFERRED_DUE_DATE,
HIDDEN_UNTIL,
BLOCKING_ON,
NOTIFICATIONS,
NOTES,
};
@ -55,6 +57,16 @@ public class TaskModelForEdit extends AbstractTaskModel {
// --- getters and setters
@Override
public Integer getNotificationIntervalSeconds() {
return super.getNotificationIntervalSeconds();
}
@Override
public void setNotificationIntervalSeconds(Integer intervalInSeconds) {
super.setNotificationIntervalSeconds(intervalInSeconds);
}
@Override
public Date getDefiniteDueDate() {
return super.getDefiniteDueDate();

@ -116,6 +116,8 @@ public class TaskModelForList extends AbstractTaskModel {
return weight;
}
// --- constructors
public TaskModelForList(Cursor cursor) {
@ -133,7 +135,7 @@ public class TaskModelForList extends AbstractTaskModel {
getTimerStart();
}
// --- getters and setters
// --- exposed getters and setters
@Override
public boolean isTaskCompleted() {
@ -208,4 +210,8 @@ public class TaskModelForList extends AbstractTaskModel {
public void stopTimerAndUpdateElapsedTime() {
super.stopTimerAndUpdateElapsedTime();
}
public static String getNameField() {
return NAME;
}
}

@ -0,0 +1,51 @@
/*
* ASTRID: Android's Simple Task Recording Dashboard
*
* Copyright (c) 2009 Tim Su
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.timsu.astrid.data.task;
import android.database.Cursor;
import com.timsu.astrid.data.AbstractController;
import com.timsu.astrid.utilities.Notifications.Notifiable;
/** Fields that you would want to see in the TaskView activity */
public class TaskModelForNotify extends AbstractTaskModel implements Notifiable {
static String[] FIELD_LIST = new String[] {
AbstractController.KEY_ROWID,
NOTIFICATIONS,
};
// --- constructors
public TaskModelForNotify(Cursor cursor) {
super(cursor);
getNotificationIntervalSeconds();
}
// --- getters and setters
@Override
public Integer getNotificationIntervalSeconds() {
return super.getNotificationIntervalSeconds();
}
}

@ -25,11 +25,12 @@ import android.database.Cursor;
import com.timsu.astrid.data.AbstractController;
import com.timsu.astrid.data.enums.Importance;
import com.timsu.astrid.utilities.Notifications.Notifiable;
/** Fields that you would want to see in the TaskView activity */
public class TaskModelForView extends AbstractTaskModel {
public class TaskModelForView extends AbstractTaskModel implements Notifiable {
static String[] FIELD_LIST = new String[] {
AbstractController.KEY_ROWID,
@ -41,6 +42,8 @@ public class TaskModelForView extends AbstractTaskModel {
TIMER_START,
DEFINITE_DUE_DATE,
PREFERRED_DUE_DATE,
CREATION_DATE,
NOTIFICATIONS,
NOTES,
};
@ -52,6 +55,11 @@ public class TaskModelForView extends AbstractTaskModel {
// --- getters and setters
@Override
public Integer getNotificationIntervalSeconds() {
return super.getNotificationIntervalSeconds();
}
@Override
public boolean isTaskCompleted() {
return super.isTaskCompleted();
@ -107,6 +115,11 @@ public class TaskModelForView extends AbstractTaskModel {
return super.getTimerStart();
}
@Override
public Date getCreationDate() {
return super.getCreationDate();
}
@Override
public void setTimerStart(Date timerStart) {
super.setTimerStart(timerStart);

@ -0,0 +1,5 @@
package com.timsu.astrid.data.task;
public class VisibilityProperty {
}

@ -0,0 +1,117 @@
package com.timsu.astrid.utilities;
import java.util.List;
import java.util.Random;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.util.Log;
import com.timsu.astrid.R;
import com.timsu.astrid.activities.TaskView;
import com.timsu.astrid.data.task.TaskController;
import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.data.task.TaskModelForNotify;
public class Notifications extends BroadcastReceiver {
private static final int MIN_INTERVAL_SECONDS = 60;
private static Random random = new Random();
@Override
/** Startup intent */
public void onReceive(Context context, Intent intent) {
NotificationManager nm = (NotificationManager) context.
getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new Notification(
android.R.drawable.stat_notify_chat, "started up",
System.currentTimeMillis());
nm.notify(0, notification);
TaskController controller = new TaskController(context);
controller.open();
List<TaskModelForNotify> tasks = controller.getTasksWithNotifications();
for(TaskModelForNotify task : tasks)
scheduleNextNotification(context, task);
}
public interface Notifiable {
public TaskIdentifier getTaskIdentifier();
public Integer getNotificationIntervalSeconds();
}
/** Schedules the next notification for this task */
public static void scheduleNextNotification(Context context,
Notifiable task) {
if(task.getNotificationIntervalSeconds() == null ||
task.getNotificationIntervalSeconds() == 0 ||
task.getTaskIdentifier() == null)
return;
// TODO if task is hidden, disregard
// add a fudge factor to mix things up a bit
int nextSeconds = (int)((random.nextFloat() * 0.2f + 0.8f) *
task.getNotificationIntervalSeconds()/60); // TODO remove /60
if(nextSeconds < MIN_INTERVAL_SECONDS)
nextSeconds = MIN_INTERVAL_SECONDS;
long when = System.currentTimeMillis() + nextSeconds * 1000;
scheduleNotification(context, task.getTaskIdentifier(), when);
}
/** Clear notifications associated with this application */
public static void clearAllNotifications(Context context, TaskIdentifier taskId) {
NotificationManager nm = (NotificationManager)
context.getSystemService(Activity.NOTIFICATION_SERVICE);
nm.cancel((int)taskId.getId());
}
/** Schedule a new notification about the given task */
public static void scheduleNotification(Context context,
TaskIdentifier taskId, long when) {
NotificationManager nm = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
Resources r = context.getResources();
Intent notifyIntent = new Intent(context, TaskView.class);
notifyIntent.putExtra(TaskView.LOAD_INSTANCE_TOKEN,
taskId.getId());
notifyIntent.putExtra(TaskView.FROM_NOTIFICATION_TOKEN, true);
PendingIntent pendingIntent = PendingIntent.getActivity(context,
0, notifyIntent, PendingIntent.FLAG_ONE_SHOT);
// notification text
String appName = r.getString(R.string.app_name);
String[] reminders = r.getStringArray(R.array.reminders);
int next = random.nextInt(reminders.length);
String reminder = reminders[next];
Notification notification = new Notification(
android.R.drawable.stat_notify_chat, reminder, when);
notification.setLatestEventInfo(context,
appName,
reminder,
pendingIntent);
notification.defaults = Notification.DEFAULT_ALL;
notification.vibrate = new long[] { 300, 50, 50, 300, 100, 300, 100,
100, 200 };
Log.w("Notifications", "Logging notification: " + reminder + " for " +
(when - System.currentTimeMillis())/1000 + " seconds from now");
nm.notify((int)taskId.getId(), notification);
}
}

@ -0,0 +1,5 @@
package com.timsu.astrid.utilities;
public class VisibilityCalculator {
}

@ -78,6 +78,7 @@ public class DateControlSet implements OnTimeSetListener,
this.date = newDate;
if(newDate == null) {
date = new Date();
date.setTime(date.getTime() + 24*3600*1000);
date.setMinutes(0);
}

@ -0,0 +1,120 @@
/*
* 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.widget;
import java.util.LinkedList;
import java.util.List;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.FrameLayout.LayoutParams;
import com.timsu.astrid.R;
/** Dialog box with an arbitrary number of number pickers */
public class NNumberPickerDialog extends AlertDialog implements OnClickListener {
public interface OnNNumberPickedListener {
void onNumbersPicked(int[] number);
}
private final List<NumberPicker> pickers = new LinkedList<NumberPicker>();
private final OnNNumberPickedListener mCallback;
/** Instantiate the dialog box.
*
* @param context
* @param callBack callback function to get the numbers you requested
* @param title title of the dialog box
* @param initialValue initial picker values array
* @param incrementBy picker increment by array
* @param start picker range start array
* @param end picker range end array
* @param separators text separating the spinners. whole array, or individual
* elements can be null
*/
public NNumberPickerDialog(Context context, OnNNumberPickedListener callBack,
String title, int[] initialValue, int[] incrementBy, int[] start,
int[] end, String[] separators) {
super(context);
mCallback = callBack;
setButton(context.getText(android.R.string.ok), this);
setButton2(context.getText(android.R.string.cancel), (OnClickListener) null);
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.n_number_picker_dialog, null);
setView(view);
LinearLayout container = (LinearLayout)view;
setTitle(title);
LayoutParams npLayout = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.FILL_PARENT);
npLayout.gravity = 1;
LayoutParams sepLayout = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.FILL_PARENT);
for(int i = 0; i < incrementBy.length; i++) {
NumberPicker np = new NumberPicker(context, null);
np.setIncrementBy(incrementBy[i]);
np.setLayoutParams(npLayout);
np.setCurrent(initialValue[i]);
np.setRange(start[i], end[i]);
container.addView(np);
pickers.add(np);
if(separators != null && separators[i] != null) {
TextView text = new TextView(context);
text.setText(separators[i]);
if(separators[i].length() < 3)
text.setTextSize(48);
else
text.setTextSize(24);
text.setGravity(Gravity.CENTER_VERTICAL);
text.setLayoutParams(sepLayout);
container.addView(text);
}
}
}
public void setInitialValues(int[] values) {
for(int i = 0; i < pickers.size(); i++)
pickers.get(i).setCurrent(values[i]);
}
public void onClick(DialogInterface dialog, int which) {
if (mCallback != null) {
int[] values = new int[pickers.size()];
for(int i = 0; i < pickers.size(); i++) {
pickers.get(i).clearFocus();
values[i] = pickers.get(i).getCurrent();
}
mCallback.onNumbersPicked(values);
}
}
}

@ -67,7 +67,7 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
}
};
private int incrementBy;
private int incrementBy = 1;
public void setIncrementBy(int incrementBy) {
this.incrementBy = incrementBy;
}
@ -102,12 +102,12 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
private int mPrevious;
private OnChangedListener mListener;
private Formatter mFormatter;
private long mSpeed = 300;
private long mSpeed = 500;
private boolean mIncrement;
private boolean mDecrement;
public NumberPicker(Context context, int incrementBy) {
public NumberPicker(Context context) {
this(context, null);
}

@ -26,30 +26,59 @@ import android.widget.Button;
import com.timsu.astrid.R;
import com.timsu.astrid.utilities.DateUtilities;
import com.timsu.astrid.widget.NumberPickerDialog.OnNumberPickedListener;
import com.timsu.astrid.widget.NNumberPickerDialog.OnNNumberPickedListener;
public class TimeDurationControlSet implements OnNumberPickedListener,
public class TimeDurationControlSet implements OnNNumberPickedListener,
View.OnClickListener {
public enum TimeDurationType {
DAYS_HOURS,
HOURS_MINUTES;
}
private final Activity activity;
private Button timeButton;
private final Button timeButton;
private final NNumberPickerDialog dialog;
private final int prefixResource;
private final TimeDurationType type;
private int timeDuration;
private final NumberPickerDialog dialog;
public TimeDurationControlSet(Activity activity, int timeButtonId) {
public TimeDurationControlSet(Activity activity, int timeButtonId,
int prefixResource, int titleResource, TimeDurationType type) {
Resources r = activity.getResources();
this.activity = activity;
this.prefixResource = prefixResource;
this.type = type;
timeButton = (Button)activity.findViewById(timeButtonId);
timeButton.setOnClickListener(this);
dialog = new NumberPickerDialog(activity, this,
activity.getResources().getString(R.string.minutes_dialog),
0, 5, 0, 999);
switch(type) {
case DAYS_HOURS:
dialog = new NNumberPickerDialog(activity, this,
activity.getResources().getString(titleResource),
new int[] {0, 0}, new int[] {1, 1}, new int[] {0, 0},
new int[] {31, 23}, new String[] {
r.getString(R.string.days),
r.getString(R.string.hours)
});
break;
case HOURS_MINUTES:
default:
dialog = new NNumberPickerDialog(activity, this,
activity.getResources().getString(titleResource),
new int[] {0, 0}, new int[] {1, 5}, new int[] {0, 0},
new int[] {99, 59}, new String[] {":", null});
break;
}
}
public int getTimeDurationInSeconds() {
return timeDuration;
}
public void setTimeElapsed(Integer timeDurationInSeconds) {
public void setTimeDuration(Integer timeDurationInSeconds) {
if(timeDurationInSeconds == null)
timeDurationInSeconds = 0;
@ -61,15 +90,35 @@ public class TimeDurationControlSet implements OnNumberPickedListener,
return;
}
timeButton.setText(DateUtilities.getDurationString(r,
String prefix = "";
if(prefixResource != 0)
prefix = r.getString(prefixResource);
timeButton.setText(prefix + " " + DateUtilities.getDurationString(r,
timeDurationInSeconds, 2));
dialog.setInitialValue(timeDuration/60);
switch(type) {
case DAYS_HOURS:
int days = timeDuration / 24 / 3600;
int hours = timeDuration / 3600 - 24 * days;
dialog.setInitialValues(new int[] {days, hours});
break;
case HOURS_MINUTES:
hours = timeDuration / 3600;
int minutes = timeDuration/60 - 60 * hours;
dialog.setInitialValues(new int[] {hours, minutes});
}
}
@Override
/** Called when NumberPicker activity is completed */
public void onNumberPicked(NumberPicker view, int value) {
setTimeElapsed(value * 60);
public void onNumbersPicked(int[] values) {
switch(type) {
case DAYS_HOURS:
setTimeDuration(values[0] * 24 * 3600 + values[1] * 3600);
break;
case HOURS_MINUTES:
setTimeDuration(values[0] * 3600 + values[1] * 60);
break;
}
}
/** Called when time button is clicked */

Loading…
Cancel
Save