diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml
index eadc62dd8..bc62f4120 100644
--- a/astrid/AndroidManifest.xml
+++ b/astrid/AndroidManifest.xml
@@ -78,6 +78,15 @@
+
+
+
+
+
+
+
diff --git a/astrid/common-src/com/commonsware/cwac/tlv/TouchListView.java b/astrid/common-src/com/commonsware/cwac/tlv/TouchListView.java
new file mode 100644
index 000000000..781176ad9
--- /dev/null
+++ b/astrid/common-src/com/commonsware/cwac/tlv/TouchListView.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (c) 2010 CommonsWare, LLC
+ * Portions 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.
+ */
+
+package com.commonsware.cwac.tlv;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.ImageView;
+import android.widget.ListView;
+
+import com.timsu.astrid.R;
+
+public class TouchListView extends ListView {
+ private ImageView mDragView;
+ private WindowManager mWindowManager;
+ private WindowManager.LayoutParams mWindowParams;
+ private int mDragPos; // which item is being dragged
+ private int mFirstDragPos; // where was the dragged item originally
+ private int mDragPoint; // at what offset inside the item did the user grab it
+ private int mCoordOffset; // the difference between screen coordinates and coordinates in this view
+ private DragListener mDragListener;
+ private DropListener mDropListener;
+ private RemoveListener mRemoveListener;
+ private int mUpperBound;
+ private int mLowerBound;
+ private int mHeight;
+ private GestureDetector mGestureDetector;
+ public static final int FLING = 0;
+ public static final int SLIDE_RIGHT = 1;
+ public static final int SLIDE_LEFT = 2;
+ private int mRemoveMode = -1;
+ private final Rect mTempRect = new Rect();
+ private Bitmap mDragBitmap;
+ private final int mTouchSlop;
+ private int mItemHeightNormal=-1;
+ private int mItemHeightExpanded=-1;
+ private int grabberId=-1;
+ private int dragndropBackgroundColor=0x00000000;
+
+ public TouchListView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TouchListView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+
+ if (attrs!=null) {
+ TypedArray a=getContext()
+ .obtainStyledAttributes(attrs,
+ R.styleable.TouchListView,
+ 0, 0);
+
+ mItemHeightNormal=a.getDimensionPixelSize(R.styleable.TouchListView_normal_height, 0);
+ mItemHeightExpanded=a.getDimensionPixelSize(R.styleable.TouchListView_expanded_height, mItemHeightNormal);
+ grabberId=a.getResourceId(R.styleable.TouchListView_grabber, -1);
+ dragndropBackgroundColor=a.getColor(R.styleable.TouchListView_dragndrop_background, 0x00000000);
+ mRemoveMode=a.getInt(R.styleable.TouchListView_remove_mode, -1);
+
+ a.recycle();
+ }
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (mRemoveListener != null && mGestureDetector == null) {
+ if (mRemoveMode == FLING) {
+ mGestureDetector = new GestureDetector(getContext(), new SimpleOnGestureListener() {
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ if (mDragView != null) {
+ if (velocityX > 1000) {
+ Rect r = mTempRect;
+ mDragView.getDrawingRect(r);
+ if ( e2.getX() > r.right * 2 / 3) {
+ // fast fling right with release near the right edge of the screen
+ stopDragging();
+ mRemoveListener.remove(mFirstDragPos);
+ unExpandViews(true);
+ }
+ }
+ // flinging while dragging should have no effect
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+ }
+ if (mDragListener != null || mDropListener != null) {
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ int x = (int) ev.getX();
+ int y = (int) ev.getY();
+ int itemnum = pointToPosition(x, y);
+ if (itemnum == AdapterView.INVALID_POSITION) {
+ break;
+ }
+ ViewGroup item = (ViewGroup) getChildAt(itemnum - getFirstVisiblePosition());
+ mDragPoint = y - item.getTop();
+ mCoordOffset = ((int)ev.getRawY()) - y;
+ View dragger = item.findViewById(grabberId);
+ Rect r = mTempRect;
+// dragger.getDrawingRect(r);
+
+ r.left=dragger.getLeft();
+ r.right=dragger.getRight();
+ r.top=dragger.getTop();
+ r.bottom=dragger.getBottom();
+
+ if ((r.left= 0; i--) {
+ final View child = getChildAt(i);
+ child.getHitRect(frame);
+ if (frame.contains(x, y)) {
+ return getFirstVisiblePosition() + i;
+ }
+ }
+ return INVALID_POSITION;
+ }
+
+ private int getItemForPosition(int y) {
+ int adjustedy = y - mDragPoint - 32;
+ int pos = myPointToPosition(0, adjustedy);
+ if (pos >= 0) {
+ if (pos <= mFirstDragPos) {
+ pos += 1;
+ }
+ } else if (adjustedy < 0) {
+ pos = 0;
+ }
+ return pos;
+ }
+
+ private void adjustScrollBounds(int y) {
+ if (y >= mHeight / 3) {
+ mUpperBound = mHeight / 3;
+ }
+ if (y <= mHeight * 2 / 3) {
+ mLowerBound = mHeight * 2 / 3;
+ }
+ }
+
+ /*
+ * Restore size and visibility for all listitems
+ */
+ private void unExpandViews(boolean deletion) {
+ for (int i = 0;; i++) {
+ View v = getChildAt(i);
+ if (v == null) {
+ if (deletion) {
+ // HACK force update of mItemCount
+ int position = getFirstVisiblePosition();
+ int y = getChildAt(0).getTop();
+ setAdapter(getAdapter());
+ setSelectionFromTop(position, y);
+ // end hack
+ }
+ layoutChildren(); // force children to be recreated where needed
+ v = getChildAt(i);
+ if (v == null) {
+ break;
+ }
+ }
+ ViewGroup.LayoutParams params = v.getLayoutParams();
+ params.height = mItemHeightNormal;
+ v.setLayoutParams(params);
+ v.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /* Adjust visibility and size to make it appear as though
+ * an item is being dragged around and other items are making
+ * room for it:
+ * If dropping the item would result in it still being in the
+ * same place, then make the dragged listitem's size normal,
+ * but make the item invisible.
+ * Otherwise, if the dragged listitem is still on screen, make
+ * it as small as possible and expand the item below the insert
+ * point.
+ * If the dragged item is not on screen, only expand the item
+ * below the current insertpoint.
+ */
+ private void doExpansion() {
+ int childnum = mDragPos - getFirstVisiblePosition();
+ if (mDragPos > mFirstDragPos) {
+ childnum++;
+ }
+
+ View first = getChildAt(mFirstDragPos - getFirstVisiblePosition());
+
+ for (int i = 0;; i++) {
+ View vv = getChildAt(i);
+ if (vv == null) {
+ break;
+ }
+ int height = mItemHeightNormal;
+ int visibility = View.VISIBLE;
+ if (vv.equals(first)) {
+ // processing the item that is being dragged
+ if (mDragPos == mFirstDragPos) {
+ // hovering over the original location
+ visibility = View.INVISIBLE;
+ } else {
+ // not hovering over it
+ height = 1;
+ }
+ } else if (i == childnum) {
+ if (mDragPos < getCount() - 1) {
+ height = mItemHeightExpanded;
+ }
+ }
+ ViewGroup.LayoutParams params = vv.getLayoutParams();
+ params.height = height;
+ vv.setLayoutParams(params);
+ vv.setVisibility(visibility);
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mGestureDetector != null) {
+ mGestureDetector.onTouchEvent(ev);
+ }
+ if ((mDragListener != null || mDropListener != null) && mDragView != null) {
+ int action = ev.getAction();
+ switch (action) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ Rect r = mTempRect;
+ mDragView.getDrawingRect(r);
+ stopDragging();
+
+ if (mRemoveMode == SLIDE_RIGHT && ev.getX() > r.left+(r.width()*3/4)) {
+ if (mRemoveListener != null) {
+ mRemoveListener.remove(mFirstDragPos);
+ }
+ unExpandViews(true);
+ } else if (mRemoveMode == SLIDE_LEFT && ev.getX() < r.left+(r.width()/4)) {
+ if (mRemoveListener != null) {
+ mRemoveListener.remove(mFirstDragPos);
+ }
+ unExpandViews(true);
+ } else {
+ if (mDropListener != null && mDragPos >= 0 && mDragPos < getCount()) {
+ mDropListener.drop(mFirstDragPos, mDragPos);
+ }
+ unExpandViews(false);
+ }
+ break;
+
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ int x = (int) ev.getX();
+ int y = (int) ev.getY();
+ dragView(x, y);
+ int itemnum = getItemForPosition(y);
+ if (itemnum >= 0) {
+ if (action == MotionEvent.ACTION_DOWN || itemnum != mDragPos) {
+ if (mDragListener != null) {
+ mDragListener.drag(mDragPos, itemnum);
+ }
+ mDragPos = itemnum;
+ doExpansion();
+ }
+ int speed = 0;
+ adjustScrollBounds(y);
+ if (y > mLowerBound) {
+ // scroll the list up a bit
+ speed = y > (mHeight + mLowerBound) / 2 ? 16 : 4;
+ } else if (y < mUpperBound) {
+ // scroll the list down a bit
+ speed = y < mUpperBound / 2 ? -16 : -4;
+ }
+ if (speed != 0) {
+ int ref = pointToPosition(0, mHeight / 2);
+ if (ref == AdapterView.INVALID_POSITION) {
+ //we hit a divider or an invisible view, check somewhere else
+ ref = pointToPosition(0, mHeight / 2 + getDividerHeight() + 64);
+ }
+ View v = getChildAt(ref - getFirstVisiblePosition());
+ if (v!= null) {
+ int pos = v.getTop();
+ setSelectionFromTop(ref, pos - speed);
+ }
+ }
+ }
+ break;
+ }
+ return true;
+ }
+ return super.onTouchEvent(ev);
+ }
+
+ private void startDragging(Bitmap bm, int y) {
+ stopDragging();
+
+ mWindowParams = new WindowManager.LayoutParams();
+ mWindowParams.gravity = Gravity.TOP;
+ mWindowParams.x = 0;
+ mWindowParams.y = y - mDragPoint + mCoordOffset;
+
+ mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+ mWindowParams.format = PixelFormat.TRANSLUCENT;
+ mWindowParams.windowAnimations = 0;
+
+ ImageView v = new ImageView(getContext());
+// int backGroundColor = getContext().getResources().getColor(R.color.dragndrop_background);
+ v.setBackgroundColor(dragndropBackgroundColor);
+ v.setImageBitmap(bm);
+ mDragBitmap = bm;
+
+ mWindowManager = (WindowManager)getContext().getSystemService("window");
+ mWindowManager.addView(v, mWindowParams);
+ mDragView = v;
+ }
+
+ private void dragView(int x, int y) {
+ float alpha = 1.0f;
+ int width = mDragView.getWidth();
+
+ if (mRemoveMode == SLIDE_RIGHT) {
+ if (x > width / 2) {
+ alpha = ((float)(width - x)) / (width / 2);
+ }
+ mWindowParams.alpha = alpha;
+ }
+ else if (mRemoveMode == SLIDE_LEFT) {
+ if (x < width / 2) {
+ alpha = ((float)x) / (width / 2);
+ }
+ mWindowParams.alpha = alpha;
+ }
+ mWindowParams.y = y - mDragPoint + mCoordOffset;
+ mWindowManager.updateViewLayout(mDragView, mWindowParams);
+ }
+
+ private void stopDragging() {
+ if (mDragView != null) {
+ WindowManager wm = (WindowManager)getContext().getSystemService("window");
+ wm.removeView(mDragView);
+ mDragView.setImageDrawable(null);
+ mDragView = null;
+ }
+ if (mDragBitmap != null) {
+ mDragBitmap.recycle();
+ mDragBitmap = null;
+ }
+ }
+
+ public void setDragListener(DragListener l) {
+ mDragListener = l;
+ }
+
+ public void setDropListener(DropListener l) {
+ mDropListener = l;
+ }
+
+ public void setRemoveListener(RemoveListener l) {
+ mRemoveListener = l;
+ }
+
+ public interface DragListener {
+ void drag(int from, int to);
+ }
+ public interface DropListener {
+ void drop(int from, int to);
+ }
+ public interface RemoveListener {
+ void remove(int which);
+ }
+}
diff --git a/astrid/res/layout/gtasks_decoration.xml b/astrid/res/layout/gtasks_decoration.xml
deleted file mode 100644
index 77d6a0f23..000000000
--- a/astrid/res/layout/gtasks_decoration.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
diff --git a/astrid/res/layout/task_list_activity.xml b/astrid/res/layout/task_list_activity.xml
index 8dc5c3531..860cafd0f 100644
--- a/astrid/res/layout/task_list_activity.xml
+++ b/astrid/res/layout/task_list_activity.xml
@@ -38,32 +38,7 @@
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/astrid/res/layout/task_list_body_api3.xml b/astrid/res/layout/task_list_body_api3.xml
new file mode 100644
index 000000000..fffe60e8f
--- /dev/null
+++ b/astrid/res/layout/task_list_body_api3.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/astrid/res/layout/task_list_body_draggable.xml b/astrid/res/layout/task_list_body_draggable.xml
new file mode 100644
index 000000000..096958c76
--- /dev/null
+++ b/astrid/res/layout/task_list_body_draggable.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/astrid/res/layout/task_list_body_standard.xml b/astrid/res/layout/task_list_body_standard.xml
new file mode 100644
index 000000000..b27f20655
--- /dev/null
+++ b/astrid/res/layout/task_list_body_standard.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/astrid/res/values/cwac_touchlist_attrs.xml b/astrid/res/values/cwac_touchlist_attrs.xml
new file mode 100644
index 000000000..e9eb6afa0
--- /dev/null
+++ b/astrid/res/values/cwac_touchlist_attrs.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/astrid/src/com/todoroo/astrid/activity/DraggableTaskListActivity.java b/astrid/src/com/todoroo/astrid/activity/DraggableTaskListActivity.java
new file mode 100644
index 000000000..5274a76bd
--- /dev/null
+++ b/astrid/src/com/todoroo/astrid/activity/DraggableTaskListActivity.java
@@ -0,0 +1,75 @@
+package com.todoroo.astrid.activity;
+
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.commonsware.cwac.tlv.TouchListView;
+import com.timsu.astrid.R;
+import com.todoroo.andlib.sql.Criterion;
+import com.todoroo.andlib.sql.Functions;
+import com.todoroo.andlib.sql.Join;
+import com.todoroo.andlib.sql.Order;
+import com.todoroo.andlib.sql.QueryTemplate;
+import com.todoroo.astrid.api.Filter;
+import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
+import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
+import com.todoroo.astrid.data.Metadata;
+import com.todoroo.astrid.data.Task;
+
+/**
+ * Activity for working with draggable task lists, like Google Tasks lists
+ *
+ * @author Tim Su
+ *
+ */
+public class DraggableTaskListActivity extends TaskListActivity {
+
+ // --- gtasks temp stuff
+ private final String listId = "17816916813445155620:0:0"; //$NON-NLS-1$
+ Filter filter = new Filter("Tim's Tasks", "Tim's Tasks", new QueryTemplate().join( //$NON-NLS-1$ //$NON-NLS-2$
+ Join.left(Metadata.TABLE, Task.ID.eq(Metadata.TASK))).where(Criterion.and(
+ MetadataCriteria.withKey("gtasks"), //$NON-NLS-1$
+ TaskCriteria.isVisible(),
+ TaskCriteria.notDeleted(),
+ Metadata.VALUE2.eq(listId))).orderBy(
+ Order.asc(Functions.cast(Metadata.VALUE5, "INTEGER"))), //$NON-NLS-1$
+ null);
+
+ // --- end
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ getIntent().putExtra(TOKEN_FILTER, filter);
+ super.onCreate(icicle);
+
+ TouchListView tlv = (TouchListView) getListView();
+ tlv.setDropListener(onDrop);
+ tlv.setRemoveListener(onRemove);
+ }
+
+ @Override
+ protected View getListBody(ViewGroup root) {
+ return getLayoutInflater().inflate(R.layout.task_list_body_draggable, root);
+ }
+
+ private final TouchListView.DropListener onDrop = new TouchListView.DropListener() {
+ @Override
+ public void drop(int from, int to) {
+ /*String item = adapter.getItem(from);
+
+ adapter.remove(item);
+ adapter.insert(item, to);*/
+ }
+ };
+
+ private final TouchListView.RemoveListener onRemove = new TouchListView.RemoveListener() {
+ @Override
+ public void remove(int which) {
+ // new GtasksIndentAction.GtasksIncreaseIndentAction().indent(adapter.getItemId(which));
+ // adapter.notifyDataSetChanged();
+ // adapter.remove(adapter.getItem(which));
+ }
+ };
+
+}
diff --git a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java
index 7f725e46a..86801ece5 100644
--- a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java
+++ b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java
@@ -3,9 +3,9 @@ package com.todoroo.astrid.activity;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;
-import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicReference;
import android.app.AlertDialog;
@@ -27,27 +27,28 @@ import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
-import android.widget.Toast;
-import android.widget.AbsListView.OnScrollListener;
-import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.TextView.OnEditorActionListener;
+import android.widget.Toast;
import com.flurry.android.FlurryAgent;
import com.timsu.astrid.R;
@@ -177,6 +178,18 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
DependencyInjectionService.getInstance().inject(this);
}
+ /**
+ * @return view to attach to the body of the task list. must contain two
+ * elements, a view with id android:id/empty and a list view with id
+ * android:id/list
+ */
+ protected View getListBody(ViewGroup root) {
+ if(AndroidUtilities.getSdkVersion() > 3)
+ return getLayoutInflater().inflate(R.layout.task_list_body_standard, root, false);
+ else
+ return getLayoutInflater().inflate(R.layout.task_list_body_api3, root, false);
+ }
+
/** Called when loading up the activity */
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -184,10 +197,9 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
super.onCreate(savedInstanceState);
new StartupService().onStartupApplication(this);
- if(AndroidUtilities.getSdkVersion() > 3)
- setContentView(R.layout.task_list_activity);
- else
- setContentView(R.layout.task_list_activity_api3);
+ ViewGroup parent = (ViewGroup) getLayoutInflater().inflate(R.layout.task_list_activity, null);
+ parent.addView(getListBody(parent), 1);
+ setContentView(parent);
if(database == null)
return;