Crazy Astrid 3 activity merged in. Does it work? not really. But it's cool that it's there.

pull/14/head
Tim Su 16 years ago
parent f8a907487f
commit 9225898806

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.timsu.astrid"
android:versionName="2.14.3" android:versionCode="134">
android:versionName="3.0.0" android:versionCode="135">
<!-- ============================ Metadata ============================ -->
@ -29,6 +29,34 @@
<supports-screens />
<application android:icon="@drawable/icon" android:label="@string/app_name" android:debuggable="false">
<!-- ========================= Activities ========================== -->
<!-- primary activity: agenda -->
<activity android:name="com.todoroo.astrid.activity.HomeActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.todoroo.astrid.activity.TaskListActivity"
android:theme="@style/Theme">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name="com.todoroo.astrid.activity.FilterListActivity"
android:theme="@style/Theme">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/filter_list_searchable" />
</activity>
<!-- ======================== Activities ========================= -->

@ -3,8 +3,6 @@ package com.todoroo.andlib.service;
import java.lang.reflect.Field;
import java.util.Arrays;
import android.util.Log;
/**
* Simple Dependency Injection Service for Android.
* <p>
@ -138,6 +136,5 @@ public class DependencyInjectionService {
*/
public synchronized void setInjectors(AbstractDependencyInjector[] injectors) {
this.injectors = injectors;
Log.e("INJECTION SETTING", "Set Injector List to: " + Arrays.asList(injectors)); // (debug)
}
}

@ -0,0 +1,58 @@
/**
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.filters;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import com.timsu.astrid.R;
import com.todoroo.astrid.activity.FilterListActivity;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterListItem;
import com.todoroo.astrid.model.Task;
/**
* Exposes Astrid's built in filters to the {@link FilterListActivity}
*
* @author Tim Su <tim@todoroo.com>
*
*/
public class FilterExposer extends BroadcastReceiver {
public static Filter buildInboxFilter(Resources r) {
return new Filter(r.getString(R.string.BFE_Inbox),
r.getString(R.string.BFE_Inbox),
/*String.format("WHERE %s AND %s ORDER BY CASE %s WHEN 0 THEN (%d + 1000 * %s) ELSE (%s + 1000 * %s) END ASC", //$NON-NLS-1$
TaskSql.isActive(), TaskSql.isVisible(DateUtilities.now()),
Task.DUE_DATE, DateUtilities.now() + 60 * 24 * 3600, Task.IMPORTANCE,
Task.DUE_DATE, Task.IMPORTANCE)*/ "",
null);
}
@Override
public void onReceive(Context context, Intent intent) {
Resources r = context.getResources();
// build filters
Filter inbox = buildInboxFilter(r);
Filter all = new Filter(r.getString(R.string.BFE_All),
r.getString(R.string.BFE_All),
String.format("ORDER BY %s DESC", //$NON-NLS-1$
Task.ID.name),
null);
// transmit filter list
FilterListItem[] list = new FilterListItem[2];
list[0] = inbox;
list[1] = all;
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ITEMS, list);
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
}
}

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:type="radial"
android:startColor="#373737"
android:endColor="#000000"
android:gradientRadius="300"
android:centerX="0.5"
android:centerY="0.5" />
</shape>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#ffffffff"
android:centerColor="#ff000000"
android:endColor="#ffffffff"
android:angle="0" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

@ -0,0 +1,20 @@
<?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">
<item
android:drawable="@drawable/none" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

@ -0,0 +1,26 @@
<?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">
<item android:state_pressed="false" android:state_enabled="true"
android:state_focused="false" android:drawable="@drawable/ic_tasklist_back" />
<item android:state_pressed="true" android:state_enabled="true"
android:drawable="@drawable/ic_tasklist_back_pressed" />
<item android:state_pressed="false" android:state_enabled="true"
android:state_focused="true" android:drawable="@drawable/ic_tasklist_back_selected" />
</selector>

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- See the file "LICENSE" for the full license governing this code. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/background_gradient">
<!-- Loading Filters label -->
<TextView android:id="@android:id/empty"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="@string/FLA_loading"
style="@style/TextAppearance.TLA_NoItems"/>
<!-- List -->
<ExpandableListView android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@null"
android:scrollbars="vertical"
/>
</FrameLayout>

@ -0,0 +1,126 @@
<?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
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/background_gradient">
<LinearLayout android:id="@+id/event"
android:paddingRight="8px"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:id="@+id/name_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/TEA_name_label"
style="@style/TextAppearance.GEN_EditLabel"/>
<EditText android:id="@+id/name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="@string/TEA_name_hint"
android:autoText="true"
android:capitalize="sentences"/>
<TextView android:id="@+id/importance_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/TEA_importance_label"
style="@style/TextAppearance.GEN_EditLabel"/>
<LinearLayout android:id="@+id/importance_container"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</LinearLayout>
<TextView android:id="@+id/urgency_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/TEA_urgency_label"
style="@style/TextAppearance.GEN_EditLabel"/>
<Spinner android:id="@+id/urgency"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<Button android:id="@+id/fixedDate"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:visibility="gone"/>
<TextView android:id="@+id/hiddenUntil_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/TEA_hiddenUntil_label"
style="@style/TextAppearance.GEN_EditLabel"/>
<Button android:id="@+id/hiddenUntil"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView android:id="@+id/plugins_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/TEA_plugins_label"
android:visibility="gone"
style="@style/TextAppearance.GEN_EditLabel"/>
<LinearLayout android:id="@+id/plugins_container"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="1dip"
android:background="@drawable/black_white_gradient"
/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="5dip"
android:baselineAligned="false">
<Button android:id="@+id/save"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/TEA_save_label"
/>
<Button android:id="@+id/discard"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/TEA_discard_label"
/>
</LinearLayout>
</LinearLayout>
</ScrollView>

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- See the file "LICENSE" for the full license governing this code. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="@drawable/background_gradient">
<!-- Header -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:background="@drawable/edit_header">
<!-- Back Button -->
<ImageView android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="@drawable/tasklist_back"
android:paddingTop="8px"
android:paddingLeft="5px"/>
<!-- List Label -->
<TextView android:id="@+id/listLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="100"
android:singleLine="true"
android:paddingTop="6px"
android:paddingRight="50px"
style="@style/TextAppearance.TLA_Header"/>
</LinearLayout>
<!-- Body -->
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="100">
<!-- No Tasks label -->
<TextView android:id="@android:id/empty"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="gone"
android:text="@string/TLA_no_items"
style="@style/TextAppearance.TLA_NoItems"/>
<!-- Task List -->
<ListView android:id="@android:id/list"
android:background="@null"
android:scrollbars="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</FrameLayout>
<!-- Footer -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal">
<!-- Quick Add Task -->
<EditText android:id="@+id/quickAddText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="100"
android:hint="@string/TLA_quick_add_hint"
android:singleLine="true"
android:autoText="true"
android:capitalize="sentences"/>
<!-- Quick Add Button -->
<ImageButton android:id="@+id/quickAddButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="@android:drawable/ic_input_add"/>
</LinearLayout>
</LinearLayout>

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- See the file "LICENSE" for the full license governing this code. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/row_layout"
android:orientation="horizontal"
android:background="@android:drawable/list_selector_background"
android:focusable="true"
android:paddingTop="2px"
android:paddingLeft="6dip"
android:paddingRight="6dip"
android:paddingBottom="2px"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<!-- completion check-box -->
<CheckBox android:id="@+id/completeBox"
android:layout_width="40px"
android:layout_height="45px"
android:layout_weight="1"
android:paddingLeft="10px" />
<LinearLayout android:id="@+id/details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="100"
android:paddingLeft="10px"
android:orientation="vertical">
<!-- task name -->
<TextView android:id="@+id/title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.TAd_ItemTitle"
android:gravity="center_vertical"/>
<!-- due date -->
<TextView android:id="@+id/dueDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"/>
<!-- other details go here -->
</LinearLayout>
<!-- importance -->
<View android:id="@+id/importance"
android:layout_width="12px"
android:layout_height="fill_parent"/>
</LinearLayout>

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- See the file "LICENSE" for the full license governing this code. -->
<resources>
<!-- Resources for built-in filter plug-in -->
<!-- Inbox Filter -->
<string name="BFE_Inbox">Inbox</string>
<!-- Completed Filter -->
<string name="BFE_Completed">Completed</string>
<!-- All Tasks Filter -->
<string name="BFE_All">All Tasks</string>
</resources>

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- See the file "LICENSE" for the full license governing this code. -->
<resources>
<!-- ============================ General ============================= -->
<style name="TextAppearance" parent="android:TextAppearance" />
<!-- generic style for labels above edit boxes -->
<style name="TextAppearance.GEN_EditLabel">
<item name="android:textSize">14sp</item>
<item name="android:textStyle">bold</item>
</style>
<style name="Theme" parent="android:Theme">
<item name="android:windowBackground">@null</item>
</style>
<!--========================= TaskListActivity ======================== -->
<style name="TextAppearance.TLA_NoItems">
<item name="android:textSize">20sp</item>
<item name="android:gravity">center</item>
</style>
<style name="TextAppearance.TLA_Header">
<item name="android:textSize">24sp</item>
<item name="android:gravity">center</item>
</style>
<!-- =========================== TaskAdapter =========================== -->
<style name="TextAppearance.TAd_ItemTitle">
<item name="android:textSize">22sp</item>
<item name="android:textStyle">bold</item>
</style>
<style name="TextAppearance.TAd_ItemTitle_Completed" parent="TextAppearance.TAd_ItemTitle">
<item name="android:textColor">#ff555555</item>
</style>
<style name="TextAppearance.TAd_ItemDetails">
<item name="android:textSize">14sp</item>
<item name="android:textColor">#ff777777</item>
</style>
<style name="TextAppearance.TAd_ItemDueDate" parent="TextAppearance.TAd_ItemDetails">
<item name="android:textColor">#ff7777aa</item>
</style>
<style name="TextAppearance.TAd_ItemDueDate_Overdue" parent="TextAppearance.TAd_ItemDueDate">
<item name="android:textColor">#ffee5555</item>
</style>
<!-- ======================== FilterListAdapter ======================== -->
<style name="TextAppearance.FLA_Filter" parent="TextAppearance.TAd_ItemTitle">
<item name="android:textSize">22sp</item>
<item name="android:paddingTop">5px</item>
<item name="android:paddingBottom">5px</item>
<item name="android:paddingLeft">10px</item>
</style>
<style name="TextAppearance.FLA_Header" parent="TextAppearance.TAd_ItemTitle">
<item name="android:textSize">18sp</item>
<item name="android:textColor">#ffdddddd</item>
</style>
<style name="TextAppearance.FLA_Category" parent="TextAppearance.TAd_ItemTitle">
<item name="android:textColor">#ff7ada24</item>
<item name="android:textStyle">bold|italic</item>
</style>
</resources>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/FLA_search_label"
android:hint="@string/FLA_search_hint">
</searchable>

@ -39,7 +39,7 @@ import com.timsu.astrid.utilities.DialogUtilities;
*/
public abstract class TaskModificationTabbedActivity<MODEL_TYPE extends
AbstractTaskModel> extends TabActivity {
protected static final String LOAD_INSTANCE_TOKEN = TaskModificationActivity.LOAD_INSTANCE_TOKEN;
public static final String LOAD_INSTANCE_TOKEN = TaskModificationActivity.LOAD_INSTANCE_TOKEN;
protected TaskController controller;
protected MODEL_TYPE model;

@ -0,0 +1,92 @@
/**
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.activity;
import android.content.Intent;
import android.os.Bundle;
import com.todoroo.andlib.data.AbstractModel;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.service.AstridDependencyInjector;
/**
* This activity displays a <code>WebView</code> that allows users to log in to the
* synchronization provider requested. A callback method determines whether
* their login was successful and therefore whether to dismiss the dialog.
*
* @author Tim Su <tim@todoroo.com>
*
*/
abstract public class AbstractModelActivity<TYPE extends AbstractModel> extends AstridActivity {
// --- bundle arguments
/**
* Action Item ID
*/
public static final String ID_TOKEN = "i"; //$NON-NLS-1$
// --- instance variables
@Autowired
protected ExceptionService exceptionService;
@Autowired
protected Database database;
protected TYPE model = null;
// --- abstract methods
abstract protected TYPE fetchModel(long id);
/**
* Load Bente Dependency Injector
*/
static {
AstridDependencyInjector.initialize();
}
public AbstractModelActivity() {
DependencyInjectionService.getInstance().inject(this);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
loadItem(getIntent());
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
loadItem(intent);
}
/**
* Loads action item from the given intent
* @param intent
*/
protected void loadItem(Intent intent) {
long idParam = intent.getLongExtra(ID_TOKEN, -1L);
if(idParam == -1) {
exceptionService.reportError("AMA-no-token", null); //$NON-NLS-1$
finish();
return;
}
database.openForReading();
model = fetchModel(idParam);
if(model == null) {
exceptionService.reportError("AMA-no-task", new NullPointerException("model")); //$NON-NLS-1$ //$NON-NLS-2$
finish();
return;
}
}
}

@ -0,0 +1,46 @@
/**
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.activity;
import android.app.Activity;
import android.os.Bundle;
import com.flurry.android.FlurryAgent;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.astrid.service.AstridDependencyInjector;
import com.todoroo.astrid.utility.Constants;
/**
* General helpers for Astrid activities
*/
abstract public class AstridActivity extends Activity {
static {
AstridDependencyInjector.initialize();
}
public AstridActivity() {
DependencyInjectionService.getInstance().inject(this);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ContextManager.setContext(this);
}
@Override
protected void onStart() {
super.onStart();
FlurryAgent.onStartSession(this, Constants.FLURRY_KEY);
}
@Override
protected void onStop() {
super.onStop();
FlurryAgent.onEndSession(this);
}
}

@ -0,0 +1,361 @@
/**
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.activity;
import android.app.ExpandableListActivity;
import android.app.SearchManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.os.Parcelable;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.EditText;
import android.widget.ExpandableListView;
import android.widget.Toast;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import com.flurry.android.FlurryAgent;
import com.timsu.astrid.R;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.adapter.FilterAdapter;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterListItem;
import com.todoroo.astrid.service.AstridDependencyInjector;
import com.todoroo.astrid.utility.Constants;
/**
* Activity that displays a user's task lists and allows users
* to filter their task list.
*
* @author Tim Su <tim@todoroo.com>
*
*/
public class FilterListActivity extends ExpandableListActivity {
// --- menu codes
private static final int MENU_SEARCH_ID = Menu.FIRST + 0;
private static final int MENU_HELP_ID = Menu.FIRST + 1;
private static final int CONTEXT_MENU_SHORTCUT = Menu.FIRST + 2;
private static final int CONTEXT_MENU_INTENT = Menu.FIRST + 3;
// --- instance variables
@Autowired
protected ExceptionService exceptionService;
@Autowired
protected DialogUtilities dialogUtilities;
FilterAdapter adapter = null;
String search = null;
FilterReceiver filterReceiver = new FilterReceiver();
/* ======================================================================
* ======================================================= initialization
* ====================================================================== */
static {
AstridDependencyInjector.initialize();
}
public FilterListActivity() {
DependencyInjectionService.getInstance().inject(this);
}
/** Called when loading up the activity */
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ContextManager.setContext(this);
setContentView(R.layout.filter_list_activity);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
setTitle(R.string.FLA_title);
setUpList();
getLists();
onNewIntent(getIntent());
}
/**
* Called when receiving a new intent. Intents this class handles:
* <ul>
* <li>ACTION_SEARCH - displays a search bar
* <li>ACTION_ADD_LIST - adds new lists to the merge adapter
* </ul>
*/
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
final String intentAction = intent.getAction();
if (Intent.ACTION_SEARCH.equals(intentAction)) {
search = intent.getStringExtra(SearchManager.QUERY);
dialogUtilities.okDialog(this, "TODO!", null); //$NON-NLS-1$
} else {
search = null;
}
}
/**
* Create options menu (displayed when user presses menu key)
*
* @return true if menu should be displayed
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if(menu.size() > 0)
return true;
MenuItem item;
item = menu.add(Menu.NONE, MENU_SEARCH_ID, Menu.NONE,
R.string.FLA_menu_search);
item.setIcon(android.R.drawable.ic_menu_search);
item = menu.add(Menu.NONE, MENU_HELP_ID, Menu.NONE,
R.string.FLA_menu_help);
item.setIcon(android.R.drawable.ic_menu_help);
return true;
}
/* ======================================================================
* ============================================================ lifecycle
* ====================================================================== */
@Override
protected void onStart() {
super.onStart();
FlurryAgent.onStartSession(this, Constants.FLURRY_KEY);
}
@Override
protected void onStop() {
super.onStop();
FlurryAgent.onEndSession(this);
}
@Override
protected void onResume() {
super.onResume();
registerReceiver(filterReceiver,
new IntentFilter(AstridApiConstants.BROADCAST_SEND_FILTERS));
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(filterReceiver);
}
/**
* Receiver which receives intents to add items to the filter list
*
* @author Tim Su <tim@todoroo.com>
*
*/
protected class FilterReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
try {
Parcelable[] filters = intent.getExtras().
getParcelableArray(AstridApiConstants.EXTRAS_ITEMS);
for (Parcelable item : filters) {
adapter.add((FilterListItem)item);
}
adapter.notifyDataSetChanged();
} catch (Exception e) {
exceptionService.reportError("receive-filter-" + //$NON-NLS-1$
intent.getStringExtra(AstridApiConstants.EXTRAS_PLUGIN), e);
}
}
}
/* ======================================================================
* ===================================================== populating lists
* ====================================================================== */
/** Sets up the coach list adapter */
protected void setUpList() {
adapter = new FilterAdapter(this);
setListAdapter(adapter);
registerForContextMenu(getExpandableListView());
getExpandableListView().setGroupIndicator(
getResources().getDrawable(R.drawable.expander_group));
}
/**
* Broadcast a request for lists. The request is sent to every
* application registered to listen for this broadcast. Each application
* can then add lists to the
*/
protected void getLists() {
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_FILTERS);
sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
}
/* ======================================================================
* ============================================================== actions
* ====================================================================== */
/**
* Handles items being clicked. Return true if item is handled.
*/
protected boolean onItemClicked(FilterListItem item) {
if(item instanceof Filter) {
Filter filter = (Filter)item;
Intent intent = new Intent(FilterListActivity.this, TaskListActivity.class);
intent.putExtra(TaskListActivity.TOKEN_FILTER, filter);
startActivity(intent);
return true;
}
return false;
}
@Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
FilterListItem item = (FilterListItem) adapter.getChild(groupPosition,
childPosition);
return onItemClicked(item);
}
@Override
public void onGroupExpand(int groupPosition) {
FilterListItem item = (FilterListItem) adapter.getGroup(groupPosition);
onItemClicked(item);
}
@Override
public void onGroupCollapse(int groupPosition) {
FilterListItem item = (FilterListItem) adapter.getGroup(groupPosition);
onItemClicked(item);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuInfo;
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
FilterListItem item;
if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
int groupPos = ExpandableListView.getPackedPositionGroup(info.packedPosition);
int childPos = ExpandableListView.getPackedPositionChild(info.packedPosition);
item = (FilterListItem) adapter.getChild(groupPos, childPos);
} else if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
int groupPos = ExpandableListView.getPackedPositionGroup(info.packedPosition);
item = (FilterListItem) adapter.getGroup(groupPos);
} else {
return;
}
MenuItem menuItem;
if(item instanceof Filter) {
Filter filter = (Filter) item;
info.targetView.setTag(filter);
menuItem = menu.add(0, CONTEXT_MENU_SHORTCUT, 0, R.string.FLA_context_shortcut);
Intent shortcutIntent = new Intent(this, TaskListActivity.class);
shortcutIntent.setAction(Intent.ACTION_VIEW);
shortcutIntent.putExtra(TaskListActivity.TOKEN_SHORTCUT_TITLE, filter.title);
shortcutIntent.putExtra(TaskListActivity.TOKEN_SHORTCUT_SQL, filter.sqlQuery);
shortcutIntent.putExtra(TaskListActivity.TOKEN_SHORTCUT_NEW_TASK_SQL, filter.sqlForNewTasks);
menuItem.setIntent(shortcutIntent);
}
for(int i = 0; i < item.contextMenuLabels.length; i++) {
if(item.contextMenuIntents.length <= i)
break;
menuItem = menu.add(0, CONTEXT_MENU_INTENT, 0, item.contextMenuLabels[i]);
menuItem.setIntent(item.contextMenuIntents[i]);
}
if(menu.size() > 0)
menu.setHeaderTitle(item.listingTitle);
}
@Override
public boolean onMenuItemSelected(int featureId, final MenuItem item) {
// handle my own menus
switch (item.getItemId()) {
case MENU_SEARCH_ID: {
onSearchRequested();
return true;
}
case MENU_HELP_ID: {
// TODO
return true;
}
case CONTEXT_MENU_SHORTCUT: {
ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo)item.getMenuInfo();
final Intent shortcutIntent = item.getIntent();
Filter filter = (Filter)info.targetView.getTag();
String dialogText = getString(R.string.FLA_shortcut_dialog);
final EditText editText = new EditText(this);
editText.setText(filter.listingTitle);
dialogUtilities.viewDialog(this, dialogText, editText,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
String label = editText.getText().toString();
Intent createShortcutIntent = new Intent();
createShortcutIntent.putExtra(
Intent.EXTRA_SHORTCUT_INTENT,
shortcutIntent);
createShortcutIntent.putExtra(
Intent.EXTRA_SHORTCUT_NAME, label);
createShortcutIntent.putExtra(
Intent.EXTRA_SHORTCUT_ICON,
((BitmapDrawable) getResources().getDrawable(
R.drawable.icon_tag)).getBitmap());
createShortcutIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); //$NON-NLS-1$
sendBroadcast(createShortcutIntent);
Toast.makeText(
FilterListActivity.this,
getString(
R.string.FLA_toast_onCreateShortcut,
label), Toast.LENGTH_LONG).show();
}
}, null);
return true;
}
case CONTEXT_MENU_INTENT: {
Intent intent = item.getIntent();
startActivity(intent);
return true;
}
}
return false;
}
}

@ -0,0 +1,82 @@
/*
* 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.todoroo.astrid.activity;
import android.content.Intent;
import android.os.Bundle;
import com.timsu.astrid.utilities.StartupReceiver;
import com.todoroo.andlib.service.ExceptionService.TodorooUncaughtExceptionHandler;
import com.todoroo.astrid.filters.FilterExposer;
/**
* HomeActivity is the primary activity for Astrid and determines which activity
* to launch next.
* <p>
* If the user is completely new, it launches {@link IntroductionActivity}.
* <p>
* If the user needs to sign in, it launches {@link SignInActivity}.
* <p>
* If the user has no coaches nd no tasks, it launches
* {@link CoachSelectorActivity}
* <p>
* Otherwise, it launches {@link TaskListActivity}.
*
* @author Tim Su <tim@todoroo.com>
*
*/
public class HomeActivity extends AstridActivity {
public static final int REQUEST_SIGN_IN = 1;
public static final int REQUEST_INTRODUCTION = 2;
/* ======================================================================
* ======================================================= initialization
* ====================================================================== */
/** Called when loading up the activity */
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// set uncaught exception handler
Thread.setDefaultUncaughtExceptionHandler(new TodorooUncaughtExceptionHandler());
// open controllers & perform application startup rituals
StartupReceiver.onStartupApplication(this);
performRedirection();
}
/* ======================================================================
* ======================================================= event handlers
* ====================================================================== */
/**
* Perform redirection to next activity
*/
private void performRedirection() {
Intent intent = new Intent(this, TaskListActivity.class);
intent.putExtra(TaskListActivity.TOKEN_FILTER, FilterExposer.buildInboxFilter(getResources()));
startActivity(intent);
finish();
}
}

@ -0,0 +1,495 @@
package com.todoroo.astrid.activity;
import java.util.List;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.widget.AbsListView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView.AdapterContextMenuInfo;
import com.flurry.android.FlurryAgent;
import com.timsu.astrid.R;
import com.timsu.astrid.activities.EditPreferences;
import com.timsu.astrid.activities.TaskEdit;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.andlib.utility.Pair;
import com.todoroo.astrid.adapter.TaskAdapter;
import com.todoroo.astrid.adapter.TaskAdapter.ViewHolder;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.TaskDetail;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.filters.FilterExposer;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.service.AstridDependencyInjector;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.utility.Constants;
/**
* Primary activity for the Bente application. Shows a list of upcoming
* tasks and a user's coaches.
*
* @author Tim Su <tim@todoroo.com>
*
*/
public class TaskListActivity extends ListActivity implements OnScrollListener {
// --- activities
static final int ACTIVITY_EDIT_TASK = 0;
static final int ACTIVITY_SETTINGS = 1;
static final int ACTIVITY_PLUGINS = 2;
// --- menu codes
private static final int MENU_ADD_TASK_ID = Menu.FIRST + 0;
private static final int MENU_PLUGINS_ID = Menu.FIRST + 1;
private static final int MENU_SETTINGS_ID = Menu.FIRST + 2;
private static final int MENU_HELP_ID = Menu.FIRST + 3;
private static final int MENU_PLUGIN_INTENT_ID = Menu.FIRST + 4;
private static final int CONTEXT_MENU_EDIT_TASK_ID = Menu.FIRST + 5;
private static final int CONTEXT_MENU_DELETE_TASK_ID = Menu.FIRST + 6;
private static final int CONTEXT_MENU_PLUGIN_INTENT_ID = Menu.FIRST + 7;
// --- constants
public static final String TOKEN_FILTER = "filter"; //$NON-NLS-1$
public static final String TOKEN_SHORTCUT_SQL = "query"; //$NON-NLS-1$
public static final String TOKEN_SHORTCUT_TITLE = "title"; //$NON-NLS-1$
public static final String TOKEN_SHORTCUT_NEW_TASK_SQL = "newsql"; //$NON-NLS-1$
// --- instance variables
@Autowired
protected ExceptionService exceptionService;
@Autowired
protected TaskService taskService;
@Autowired
protected DialogUtilities dialogUtilities;
@Autowired
protected Database database;
protected TaskAdapter taskAdapter = null;
protected DetailReceiver detailReceiver = new DetailReceiver();
Filter filter;
/* ======================================================================
* ======================================================= initialization
* ====================================================================== */
/**
* Load Bente Dependency Injector
*/
static {
AstridDependencyInjector.initialize();
}
public TaskListActivity() {
DependencyInjectionService.getInstance().inject(this);
}
/** Called when loading up the activity */
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.task_list_activity);
Bundle extras = getIntent().getExtras();
if(extras.containsKey(TOKEN_FILTER)) {
filter = extras.getParcelable(TOKEN_FILTER);
} else if(extras.containsKey(TOKEN_SHORTCUT_SQL)) {
filter = new Filter();
filter.sqlQuery = extras.getString(TOKEN_SHORTCUT_SQL);
filter.title = extras.getString(TOKEN_SHORTCUT_TITLE);
filter.sqlForNewTasks = extras.getString(TOKEN_SHORTCUT_NEW_TASK_SQL);
} else {
filter = FilterExposer.buildInboxFilter(getResources());
}
database.openForWriting();
setUpUiComponents();
setUpTaskList();
// cache some stuff
new Thread(new Runnable() {
public void run() {
loadContextMenuIntents();
}
});
}
/**
* Create options menu (displayed when user presses menu key)
*
* @return true if menu should be displayed
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if(menu.size() > 0)
return true;
MenuItem item;
item = menu.add(Menu.NONE, MENU_ADD_TASK_ID, Menu.NONE,
R.string.TLA_menu_add);
item.setIcon(android.R.drawable.ic_menu_add);
item = menu.add(Menu.NONE, MENU_PLUGINS_ID, Menu.NONE,
R.string.TLA_menu_plugins);
item.setIcon(android.R.drawable.ic_menu_set_as);
item = menu.add(Menu.NONE, MENU_SETTINGS_ID, Menu.NONE,
R.string.TLA_menu_settings);
item.setIcon(android.R.drawable.ic_menu_preferences);
item = menu.add(Menu.NONE, MENU_HELP_ID, Menu.NONE,
R.string.TLA_menu_help);
item.setIcon(android.R.drawable.ic_menu_help);
// ask about plug-ins
Intent queryIntent = new Intent(AstridApiConstants.ACTION_TASK_LIST_MENU);
PackageManager pm = getPackageManager();
List<ResolveInfo> resolveInfoList = pm.queryIntentActivities(queryIntent, 0);
int length = resolveInfoList.size();
for(int i = 0; i < length; i++) {
ResolveInfo resolveInfo = resolveInfoList.get(i);
item = menu.add(Menu.NONE, MENU_PLUGIN_INTENT_ID, Menu.NONE,
resolveInfo.loadLabel(pm));
item.setIcon(resolveInfo.loadIcon(pm));
Intent intent = new Intent(AstridApiConstants.ACTION_TASK_LIST_MENU);
intent.setClassName(resolveInfo.activityInfo.packageName,
resolveInfo.activityInfo.name);
item.setIntent(intent);
}
return true;
}
private void setUpUiComponents() {
((ImageView)findViewById(R.id.back)).setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(TaskListActivity.this,
FilterListActivity.class);
startActivity(intent);
finish();
}
});
((TextView)findViewById(R.id.listLabel)).setText(filter.title);
((ImageButton)findViewById(R.id.quickAddButton)).setOnClickListener(new OnClickListener() {
public void onClick(View v) {
TextView quickAdd = (TextView)findViewById(R.id.quickAddText);
Task task = quickAddTask(quickAdd.getText().toString());
if(quickAdd.getText().length() > 0) {
quickAdd.setText(""); //$NON-NLS-1$
loadTaskListContent(true);
} else {
Intent intent = new Intent(TaskListActivity.this, TaskEdit.class);
intent.putExtra(TaskEdit.LOAD_INSTANCE_TOKEN, task.getId());
startActivityForResult(intent, ACTIVITY_EDIT_TASK);
}
}
});
}
/**
* Quick-add a new task
* @param title
* @return
*/
@SuppressWarnings("nls")
protected Task quickAddTask(String title) {
try {
Task task = new Task();
task.setValue(Task.TITLE, title);
/*task.setValue(Task.DUE_DATE, Task.initializeDueDate(
task.getValue(Task.URGENCY)));
taskService.save(task, false);
if(filter.sqlForNewTasks != null)
taskService.invokeSqlForNewTask(filter, task); */ // TODO
return task;
} catch (Exception e) {
exceptionService.displayAndReportError(this, "quick-add-task", e);
return new Task();
}
}
/* ======================================================================
* ============================================================ lifecycle
* ====================================================================== */
@Override
protected void onStart() {
super.onStart();
FlurryAgent.onStartSession(this, Constants.FLURRY_KEY);
}
@Override
protected void onStop() {
super.onStop();
FlurryAgent.onEndSession(this);
}
@Override
protected void onResume() {
super.onResume();
registerReceiver(detailReceiver,
new IntentFilter(AstridApiConstants.BROADCAST_SEND_DETAILS));
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(detailReceiver);
}
/**
* Receiver which receives intents to add items to the filter list
*
* @author Tim Su <tim@todoroo.com>
*
*/
protected class DetailReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
try {
Bundle extras = intent.getExtras();
long taskId = extras.getLong(AstridApiConstants.EXTRAS_TASK_ID);
Parcelable[] details = extras.getParcelableArray(AstridApiConstants.EXTRAS_ITEMS);
for(Parcelable detail : details)
taskAdapter.addDetails(getListView(), taskId, (TaskDetail)detail);
} catch (Exception e) {
exceptionService.reportError("receive-detail-" + //$NON-NLS-1$
intent.getStringExtra(AstridApiConstants.EXTRAS_PLUGIN), e);
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// if(requestCode == ACTIVITY_EDIT_TASK && resultCode != TaskEditActivity.RESULT_CODE_DISCARDED)
// loadTaskListContent(true);
}
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
// do nothing
}
/**
* Detect when user is flinging the task, disable task adapter loading
* when this occurs to save resources and time.
*/
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case OnScrollListener.SCROLL_STATE_IDLE:
if(taskAdapter.isFling)
taskAdapter.notifyDataSetChanged();
taskAdapter.isFling = false;
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
if(taskAdapter.isFling)
taskAdapter.notifyDataSetChanged();
taskAdapter.isFling = false;
break;
case OnScrollListener.SCROLL_STATE_FLING:
taskAdapter.isFling = true;
break;
}
}
/* ======================================================================
* =================================================== managing list view
* ====================================================================== */
/**
* Load or re-load action items and update views
* @param requery
*/
public void loadTaskListContent(boolean requery) {
int oldListItemSelected = getListView().getSelectedItemPosition();
Cursor taskCursor = taskAdapter.getCursor();
if(requery) {
taskCursor.requery();
taskAdapter.notifyDataSetChanged();
}
if(oldListItemSelected != ListView.INVALID_POSITION &&
oldListItemSelected < taskCursor.getCount())
getListView().setSelection(oldListItemSelected);
}
/** Fill in the Action Item List with current items */
protected void setUpTaskList() {
// perform query
TodorooCursor<Task> currentCursor = taskService.fetchFiltered(
TaskAdapter.PROPERTIES, filter);
startManagingCursor(currentCursor);
// set up list adapters
taskAdapter = new TaskAdapter(this, R.layout.task_row,
currentCursor, false, null);
setListAdapter(taskAdapter);
getListView().setOnScrollListener(this);
registerForContextMenu(getListView());
loadTaskListContent(false);
}
/* ======================================================================
* ============================================================== actions
* ====================================================================== */
protected Pair<CharSequence, Intent>[] contextMenuItemCache = null;
protected void loadContextMenuIntents() {
Intent queryIntent = new Intent(AstridApiConstants.ACTION_TASK_CONTEXT_MENU);
PackageManager pm = getPackageManager();
List<ResolveInfo> resolveInfoList = pm.queryIntentActivities(queryIntent, 0);
int length = resolveInfoList.size();
contextMenuItemCache = new Pair[length];
for(int i = 0; i < length; i++) {
ResolveInfo resolveInfo = resolveInfoList.get(i);
Intent intent = new Intent(AstridApiConstants.ACTION_TASK_CONTEXT_MENU);
intent.setClassName(resolveInfo.activityInfo.packageName,
resolveInfo.activityInfo.name);
CharSequence title = resolveInfo.loadLabel(pm);
contextMenuItemCache[i] = Pair.create(title, intent);
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
AdapterContextMenuInfo adapterInfo = (AdapterContextMenuInfo)menuInfo;
Task task = ((ViewHolder)adapterInfo.targetView.getTag()).task;
int id = (int)task.getId();
menu.setHeaderTitle(task.getValue(Task.TITLE));
menu.add(id, CONTEXT_MENU_EDIT_TASK_ID, Menu.NONE,
R.string.TAd_contextEditTask);
menu.add(id, CONTEXT_MENU_DELETE_TASK_ID, Menu.NONE,
R.string.TAd_contextDeleteTask);
if(contextMenuItemCache == null)
return;
// ask about plug-ins
long taskId = task.getId();
for(int i = 0; i < contextMenuItemCache.length; i++) {
Intent intent = contextMenuItemCache[i].getRight();
MenuItem item = menu.add(id, CONTEXT_MENU_PLUGIN_INTENT_ID, Menu.NONE,
contextMenuItemCache[i].getLeft());
intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
item.setIntent(intent);
}
}
/** Show a dialog box and delete the task specified */
private void deleteTask(final long id) {
new AlertDialog.Builder(this).setTitle(R.string.DLG_confirm_title)
.setMessage(R.string.DLG_delete_this_task_question).setIcon(
android.R.drawable.ic_dialog_alert).setPositiveButton(
android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
taskService.delete(id);
loadTaskListContent(true);
}
}).setNegativeButton(android.R.string.cancel, null)
.show();
}
@Override
public boolean onMenuItemSelected(int featureId, final MenuItem item) {
Intent intent;
long itemId;
// handle my own menus
switch (item.getItemId()) {
case MENU_ADD_TASK_ID:
intent = new Intent(TaskListActivity.this, TaskEdit.class);
intent.putExtra(TaskEdit.LOAD_INSTANCE_TOKEN, Task.NO_ID);
startActivityForResult(intent, ACTIVITY_EDIT_TASK);
return true;
case MENU_PLUGINS_ID:
dialogUtilities.okDialog(
this,
"if this were real life, I would display the Android " + //$NON-NLS-1$
"market with a search for plugins that you could install.", //$NON-NLS-1$
null);
return true;
case MENU_SETTINGS_ID:
intent = new Intent(this, EditPreferences.class);
startActivityForResult(intent, ACTIVITY_SETTINGS);
return true;
case MENU_HELP_ID:
// TODO
return true;
// context menu items
case CONTEXT_MENU_PLUGIN_INTENT_ID: {
intent = item.getIntent();
AndroidUtilities.startExternalIntent(this, intent);
return true;
}
case CONTEXT_MENU_EDIT_TASK_ID: {
itemId = item.getGroupId();
intent = new Intent(TaskListActivity.this, TaskEdit.class);
intent.putExtra(TaskEdit.LOAD_INSTANCE_TOKEN, itemId);
startActivityForResult(intent, ACTIVITY_EDIT_TASK);
return true;
}
case CONTEXT_MENU_DELETE_TASK_ID:
itemId = item.getGroupId();
deleteTask(itemId);
return true;
}
return false;
}
}

@ -0,0 +1,173 @@
/**
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.adapter;
import java.util.ArrayList;
import android.app.Activity;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.AbsListView;
import android.widget.BaseExpandableListAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.timsu.astrid.R;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterCategory;
import com.todoroo.astrid.api.FilterListHeader;
import com.todoroo.astrid.api.FilterListItem;
public class FilterAdapter extends BaseExpandableListAdapter {
private final ArrayList<FilterListItem> items;
protected final Activity activity;
public FilterAdapter(Activity activity) {
super();
this.activity = activity;
this.items = new ArrayList<FilterListItem>();
}
public boolean hasStableIds() {
return true;
}
public void add(FilterListItem item) {
items.add(item);
}
/* ======================================================================
* ========================================================== child nodes
* ====================================================================== */
public Object getChild(int groupPosition, int childPosition) {
FilterListItem item = items.get(groupPosition);
if(!(item instanceof FilterCategory))
return null;
return ((FilterCategory)item).children[childPosition];
}
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
public int getChildrenCount(int groupPosition) {
FilterListItem item = items.get(groupPosition);
if(!(item instanceof FilterCategory))
return 0;
return ((FilterCategory)item).children.length;
}
public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
View convertView, ViewGroup parent) {
FilterListItem item = (FilterListItem)getChild(groupPosition, childPosition);
View textView = getFilterView((Filter)item, true);
return textView;
}
/* ======================================================================
* ========================================================= parent nodes
* ====================================================================== */
public Object getGroup(int groupPosition) {
return items.get(groupPosition);
}
public int getGroupCount() {
return items.size();
}
public long getGroupId(int groupPosition) {
return groupPosition;
}
public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
ViewGroup parent) {
View view = getView((FilterListItem)getGroup(groupPosition), false, isExpanded);
return view;
}
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
/* ======================================================================
* ================================================================ views
* ====================================================================== */
public View getView(FilterListItem item, boolean isChild, boolean isExpanded) {
if(item instanceof FilterListHeader)
return getHeaderView((FilterListHeader)item, isChild);
else if(item instanceof FilterCategory)
return getCategoryView((FilterCategory)item, isExpanded);
else if(item instanceof Filter)
return getFilterView((Filter)item, isChild);
else
throw new UnsupportedOperationException("unknown item type"); //$NON-NLS-1$
}
public View getCategoryView(FilterCategory filter, boolean isExpanded) {
AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, 64);
FrameLayout layout = new FrameLayout(activity);
layout.setLayoutParams(lp);
ImageView image = new ImageView(activity);
if(isExpanded)
image.setImageResource(R.drawable.expander_ic_maximized);
else
image.setImageResource(R.drawable.expander_ic_minimized);
FrameLayout.LayoutParams llp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.FILL_PARENT);
llp.gravity = Gravity.CENTER_VERTICAL;
image.setLayoutParams(llp);
layout.addView(image);
TextView textView = new TextView(activity);
textView.setGravity(Gravity.CENTER_VERTICAL);
textView.setLayoutParams(llp);
textView.setPadding(40, 0, 0, 0);
textView.setText(filter.listingTitle);
textView.setTextAppearance(activity, R.style.TextAppearance_FLA_Category);
layout.addView(textView);
return layout;
}
public TextView getFilterView(Filter filter, boolean isChild) {
AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, 64);
TextView textView = new TextView(activity);
textView.setBackgroundDrawable(null);
textView.setLayoutParams(lp);
textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
textView.setPadding(isChild ? 40 : 10, 0, 0, 0);
textView.setText(filter.listingTitle);
textView.setTextAppearance(activity, R.style.TextAppearance_FLA_Filter);
return textView;
}
public TextView getHeaderView(FilterListHeader header, boolean isChild) {
AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, 40);
TextView textView = new TextView(activity);
textView.setLayoutParams(lp);
textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
textView.setPadding(isChild ? 40 : 10, 0, 0, 0);
textView.setTextAppearance(activity, R.style.TextAppearance_FLA_Header);
textView.setBackgroundResource(R.drawable.edit_titlebar);
textView.setText(header.listingTitle);
return textView;
}
}

@ -0,0 +1,421 @@
package com.todoroo.astrid.adapter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnCreateContextMenuListener;
import android.widget.CheckBox;
import android.widget.CursorAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import com.timsu.astrid.R;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.TaskDetail;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.service.TaskService;
/**
* Adapter for displaying a user's tasks as a list
*
* @author Tim Su <tim@todoroo.com>
*
*/
public class TaskAdapter extends CursorAdapter {
public interface OnCompletedTaskListener {
public void onCompletedTask(Task item, boolean newState);
}
// --- other constants
/** Properties that need to be read from the action item */
public static final Property<?>[] PROPERTIES = new Property<?>[] {
Task.ID,
Task.TITLE,
Task.IMPORTANCE,
Task.URGENCY,
Task.DUE_DATE,
Task.COMPLETION_DATE,
Task.HIDDEN_UNTIL,
};
private static int[] IMPORTANCE_COLORS = null;
// --- instance variables
@Autowired
ExceptionService exceptionService;
@Autowired
TaskService taskService;
@Autowired
DialogUtilities dialogUtilities;
@Autowired
Boolean debug;
protected final Activity activity;
protected final HashMap<Long, Boolean> completedItems;
protected final HashMap<Long, ArrayList<TaskDetail>> detailCache;
public boolean isFling = false;
private final int resource;
private final LayoutInflater inflater;
protected OnCompletedTaskListener onCompletedTaskListener = null;
/**
* Constructor
*
* @param activity
* @param resource
* layout resource to inflate
* @param c
* database cursor
* @param autoRequery
* whether cursor is automatically re-queried on changes
* @param onCompletedTaskListener
* task listener. can be null
*/
public TaskAdapter(Activity activity, int resource,
TodorooCursor<Task> c, boolean autoRequery,
OnCompletedTaskListener onCompletedTaskListener) {
super(activity, c, autoRequery);
DependencyInjectionService.getInstance().inject(this);
inflater = (LayoutInflater) activity.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
this.resource = resource;
this.activity = activity;
this.onCompletedTaskListener = onCompletedTaskListener;
completedItems = new HashMap<Long, Boolean>();
detailCache = new HashMap<Long, ArrayList<TaskDetail>>();
if(IMPORTANCE_COLORS == null) {
IMPORTANCE_COLORS = new int[] { Color.RED, Color.BLUE, Color.YELLOW, Color.GREEN }; //Task.getImportanceColors(activity); // TODO
}
}
/* ======================================================================
* =========================================================== view setup
* ====================================================================== */
/** Creates a new view for use in the list view */
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = inflater.inflate(resource, parent, false);
// create view holder
ViewHolder viewHolder = new ViewHolder();
viewHolder.task = new Task();
viewHolder.nameView = (TextView)view.findViewById(R.id.title);
viewHolder.completeBox = (CheckBox)view.findViewById(R.id.completeBox);
viewHolder.dueDate = (TextView)view.findViewById(R.id.dueDate);
viewHolder.details = (LinearLayout)view.findViewById(R.id.details);
viewHolder.importance = (View)view.findViewById(R.id.importance);
view.setTag(viewHolder);
// add UI component listeners
addListeners(view);
// populate view content
bindView(view, context, cursor);
return view;
}
/** Populates a view with content */
@Override
public void bindView(View view, Context context, Cursor c) {
TodorooCursor<Task> cursor = (TodorooCursor<Task>)c;
Task actionItem = ((ViewHolder)view.getTag()).task;
actionItem.readFromCursor(cursor);
setFieldContentsAndVisibility(view, actionItem);
setTaskAppearance(view, actionItem.isCompleted());
}
/** Helper method to set the visibility based on if there's stuff inside */
private static void setVisibility(TextView v) {
if(v.getText().length() > 0)
v.setVisibility(View.VISIBLE);
else
v.setVisibility(View.GONE);
}
/**
* View Holder saves a lot of findViewById lookups.
*
* @author Tim Su <tim@todoroo.com>
*
*/
public static class ViewHolder {
public Task task;
public TextView nameView;
public CheckBox completeBox;
public TextView dueDate;
public LinearLayout details;
public View importance;
public TextView loadingDetails;
}
/** Helper method to set the contents and visibility of each field */
private void setFieldContentsAndVisibility(View view, Task task) {
Resources r = activity.getResources();
ViewHolder viewHolder = (ViewHolder)view.getTag();
// don't bother instantiating views if we're busy
if(isFling) {
viewHolder.details.removeViews(2, viewHolder.details.getChildCount() - 2);
viewHolder.dueDate.setVisibility(View.INVISIBLE);
viewHolder.nameView.setText(R.string.TAd_isFling);
viewHolder.importance.setVisibility(View.INVISIBLE);
viewHolder.completeBox.setVisibility(View.INVISIBLE);
return;
}
// name
final TextView nameView = viewHolder.nameView; {
String nameValue = task.getValue(Task.TITLE);
int hiddenUntil = task.getValue(Task.HIDDEN_UNTIL);
if(hiddenUntil > DateUtilities.now())
nameValue = r.getString(R.string.TAd_hiddenFormat, nameValue);
nameView.setText(nameValue);
}
// complete box
final CheckBox completeBox = viewHolder.completeBox; {
// show item as completed if it was recently checked
if(completedItems.containsKey(task.getId()))
task.setValue(Task.COMPLETION_DATE, DateUtilities.now());
completeBox.setChecked(task.isCompleted());
completeBox.setVisibility(View.VISIBLE);
}
// due date / completion date
final TextView dueDateView = viewHolder.dueDate; {
if(!task.isCompleted() && task.hasDueDate()) {
int dueDate = task.getValue(Task.DUE_DATE);
int secondsLeft = dueDate - DateUtilities.now();
if(secondsLeft > 0) {
dueDateView.setTextAppearance(activity, R.style.TextAppearance_TAd_ItemDueDate);
} else {
dueDateView.setTextAppearance(activity, R.style.TextAppearance_TAd_ItemDueDate_Overdue);
}
String dateValue;
Date dueDateAsDate = DateUtilities.unixtimeToDate(dueDate);
dateValue = SimpleDateFormat.getDateInstance(
SimpleDateFormat.MEDIUM).format(dueDateAsDate);
if (task.getValue(Task.URGENCY) == Task.URGENCY_SPECIFIC_DAY_TIME) {
String timeValue = SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(
dueDateAsDate);
String string = r.getString(R.string.TAd_dueDateTime).replace(
"$D", dateValue).replace("$T", timeValue); //$NON-NLS-1$ //$NON-NLS-2$
dueDateView.setText(string);
} else {
String string = r.getString(R.string.TAd_dueDate).replace(
"$D", dateValue); //$NON-NLS-1$
dueDateView.setText(string);
}
setVisibility(dueDateView);
} else {
dueDateView.setVisibility(View.GONE);
}
}
// other information - send out a request for it
final LinearLayout detailsView = viewHolder.details; {
detailsView.removeViews(2, detailsView.getChildCount() - 2);
if(detailCache.containsKey(task.getId())) {
ArrayList<TaskDetail> details = detailCache.get(task.getId());
int length = details.size();
for(int i = 0; i < length; i++)
detailsView.addView(detailToView(details.get(i)));
} else {
detailCache.put(task.getId(), new ArrayList<TaskDetail>());
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DETAILS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId());
activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
// add loading message
if(viewHolder.loadingDetails == null) {
viewHolder.loadingDetails = new TextView(activity);
viewHolder.loadingDetails.setTextAppearance(activity, R.style.TextAppearance_TAd_ItemDetails);
viewHolder.loadingDetails.setText(R.string.TAd_loading);
detailsView.addView(viewHolder.loadingDetails);
}
}
}
// importance bar - must be set at end when view height is determined
final View importanceView = viewHolder.importance; {
int value = task.getValue(Task.IMPORTANCE);
importanceView.setBackgroundColor(IMPORTANCE_COLORS[value]);
importanceView.setVisibility(View.VISIBLE);
}
}
/**
* Respond to a request to add details for a task
*
* @param taskId
*/
public synchronized void addDetails(ListView list, long taskId, TaskDetail detail) {
if(detail == null)
return;
ArrayList<TaskDetail> details = detailCache.get(taskId);
details.add(detail);
// update view if it is visible
int length = list.getChildCount();
for(int i = 0; i < length; i++) {
ViewHolder viewHolder = (ViewHolder) list.getChildAt(i).getTag();
if(viewHolder == null || viewHolder.task.getId() != taskId)
continue;
viewHolder.details.addView(detailToView(detail));
viewHolder.details.setVisibility(View.VISIBLE);
if(viewHolder.loadingDetails != null) {
viewHolder.details.removeView(viewHolder.loadingDetails);
viewHolder.loadingDetails = null;
}
break;
}
}
/**
* Create a new view for the given detail
*
* @param detail
*/
private View detailToView(TaskDetail detail) {
TextView textView = new TextView(activity);
textView.setTextAppearance(activity, R.style.TextAppearance_TAd_ItemDetails);
textView.setText(detail.text);
if(detail.color != 0)
textView.setTextColor(detail.color);
return textView;
}
private View.OnClickListener completeBoxListener = new View.OnClickListener() {
public void onClick(View v) {
View container = (View) v.getParent();
Task task = ((ViewHolder)container.getTag()).task;
completeTask(task, ((CheckBox)v).isChecked());
// set check box to actual action item state
setTaskAppearance(container, task.isCompleted());
}
};
protected ContextMenuListener listener = new ContextMenuListener();
/**
* Set listeners for this view. This is called once per view when it is
* created.
*/
private void addListeners(final View container) {
final CheckBox completeBox = ((CheckBox)container.findViewById(R.id.completeBox));
completeBox.setOnClickListener(completeBoxListener);
container.setOnCreateContextMenuListener(listener);
}
class ContextMenuListener implements OnCreateContextMenuListener {
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
// this is all a big sham. it's actually handled in Task List Activity
}
}
/* ======================================================================
* ======================================================= event handlers
* ====================================================================== */
/**
* Call me when the parent presses trackpad
*/
public void onTrackpadPressed(View container) {
if(container == null)
return;
final CheckBox completeBox = ((CheckBox)container.findViewById(R.id.completeBox));
completeBox.performClick();
}
/** Helper method to adjust a tasks' appearance if the task is completed or
* uncompleted.
*
* @param actionItem
* @param name
* @param progress
*/
void setTaskAppearance(View container, boolean state) {
CheckBox completed = (CheckBox)container.findViewById(R.id.completeBox);
TextView name = (TextView)container.findViewById(R.id.title);
completed.setChecked(state);
if(state) {
name.setPaintFlags(name.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
name.setTextAppearance(activity, R.style.TextAppearance_TAd_ItemTitle_Completed);
} else {
name.setPaintFlags(name.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
name.setTextAppearance(activity, R.style.TextAppearance_TAd_ItemTitle);
}
}
/**
* This method is called when user completes a task via check box or other
* means
*
* @param container
* container for the action item
* @param newState
* state that this task should be set to
* @param completeBox
* the box that was clicked. can be null
*/
protected void completeTask(final Task actionItem, final boolean newState) {
if(actionItem == null)
return;
if (newState != actionItem.isCompleted()) {
completedItems.put(actionItem.getId(), newState);
taskService.setComplete(actionItem, newState);
if(onCompletedTaskListener != null)
onCompletedTaskListener.onCompletedTask(actionItem, newState);
}
}
}

@ -62,6 +62,11 @@ public class AstridDependencyInjector implements AbstractDependencyInjector {
injectables.put("exceptionService", ExceptionService.class);
injectables.put("errorDialogTitleResource", R.string.DLG_error);
// TODO
injectables.put("errorDialogBodyGeneric", R.string.DLG_error);
injectables.put("errorDialogBodyNullError", R.string.DLG_error);
injectables.put("errorDialogBodySocketTimeout", R.string.DLG_error);
// com.todoroo.android.utility
injectables.put("dialogUtilities", DialogUtilities.class);
injectables.put("informationDialogTitleResource", R.string.DLG_information_title);

@ -6,6 +6,7 @@ import com.todoroo.andlib.data.sql.Query;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
import com.todoroo.astrid.model.Task;
@ -91,4 +92,9 @@ public class TaskService {
}
}
public TodorooCursor<Task> fetchFiltered(Property<?>[] properties,
Filter filter) {
return taskDao.query(Query.select(properties));
}
}

@ -22,6 +22,9 @@ public final class UpgradeService {
if(from < 1)
return;
if(from < 135)
new Astrid2To3UpgradeHelper().upgrade2To3();
// display changelog
showChangeLog(from);
}
@ -60,6 +63,12 @@ public final class UpgradeService {
"Fixed crashes occuring with certain languages (Swedish, Turkish)",
"Fixed other crashes that users have reported",
});
if(from <= 134)
newVersionString(changeLog, "3.0.0 (?/??/10)", new String[] {
"Astrid is brand new under the hood! You won't see many " +
"changes yet but Astrid received a much-needed makeover " +
"that allows it to do a lot of new tricks. Stay tuned!",
});
if(changeLog.length() == 0)
return;

Loading…
Cancel
Save