blob: 16792e6b984233424bc0b06b20ebf47cbd4bf1c4 [file] [log] [blame]
Mindy Pereiraf6a6b502012-03-15 15:24:26 -07001/*
2 * Copyright (C) 2012 Google Inc.
3 * Licensed to The Android Open Source Project.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.mail.ui;
19
20import android.content.Context;
Mindy Pereiraf9138942012-04-05 19:25:31 -070021import android.content.res.Configuration;
Mindy Pereirabcd784c2012-08-10 09:53:24 -070022import android.content.res.Resources;
Mindy Pereira8db7e402012-07-13 10:32:47 -070023import android.net.Uri;
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070024import android.util.AttributeSet;
25import android.view.MotionEvent;
26import android.view.View;
Mindy Pereiraf9138942012-04-05 19:25:31 -070027import android.view.ViewConfiguration;
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070028import android.widget.ListView;
29
30import com.android.mail.R;
Paul Westbrookbf232c32012-04-18 03:17:41 -070031import com.android.mail.browse.ConversationCursor;
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070032import com.android.mail.browse.ConversationItemView;
Mindy Pereira1ef988f2012-07-24 12:06:03 -070033import com.android.mail.browse.SwipeableConversationItemView;
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070034import com.android.mail.providers.Conversation;
Mindy Pereira06642fa2012-07-12 16:23:27 -070035import com.android.mail.providers.Folder;
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070036import com.android.mail.ui.SwipeHelper.Callback;
Paul Westbrookb334c902012-06-25 11:42:46 -070037import com.android.mail.utils.LogTag;
Mindy Pereirab0219eb2012-07-27 10:48:00 -070038import com.android.mail.utils.LogUtils;
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070039
Mindy Pereira866d3192012-03-26 09:50:00 -070040import java.util.ArrayList;
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070041import java.util.Collection;
Mindy Pereira8db7e402012-07-13 10:32:47 -070042import java.util.HashMap;
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070043
Mindy Pereira8937bf12012-07-23 14:05:02 -070044public class SwipeableListView extends ListView implements Callback {
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070045 private SwipeHelper mSwipeHelper;
Mindy Pereiraff9aff32012-04-03 15:40:05 -070046 private boolean mEnableSwipe = false;
Andy Huang489dd222012-03-29 14:52:55 -070047
Paul Westbrookb334c902012-06-25 11:42:46 -070048 public static final String LOG_TAG = LogTag.getLogTag();
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070049
Mindy Pereira866d3192012-03-26 09:50:00 -070050 private ConversationSelectionSet mConvSelectionSet;
Mindy Pereira6c72a782012-04-07 14:29:15 -070051 private int mSwipeAction;
Mindy Pereira06642fa2012-07-12 16:23:27 -070052 private Folder mFolder;
Mindy Pereira866d3192012-03-26 09:50:00 -070053
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070054 public SwipeableListView(Context context) {
55 this(context, null);
56 }
57
58 public SwipeableListView(Context context, AttributeSet attrs) {
59 this(context, attrs, -1);
60 }
61
62 public SwipeableListView(Context context, AttributeSet attrs, int defStyle) {
63 super(context, attrs, defStyle);
64 float densityScale = getResources().getDisplayMetrics().density;
Mindy Pereiraf9138942012-04-05 19:25:31 -070065 float pagingTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop();
Mindy Pereira6f0b2be2012-07-27 15:03:38 -070066 mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this, densityScale,
67 pagingTouchSlop);
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070068 }
69
Mindy Pereiraf9138942012-04-05 19:25:31 -070070 @Override
71 protected void onConfigurationChanged(Configuration newConfig) {
72 super.onConfigurationChanged(newConfig);
73 float densityScale = getResources().getDisplayMetrics().density;
74 mSwipeHelper.setDensityScale(densityScale);
75 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
76 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
77 }
78
Mindy Pereiraff9aff32012-04-03 15:40:05 -070079 /**
80 * Enable swipe gestures.
81 */
82 public void enableSwipe(boolean enable) {
83 mEnableSwipe = enable;
84 }
85
Mindy Pereiraf07269f2012-04-04 15:22:07 -070086 public boolean isSwipeEnabled() {
87 return mEnableSwipe;
88 }
89
Mindy Pereira6c72a782012-04-07 14:29:15 -070090 public void setSwipeAction(int action) {
91 mSwipeAction = action;
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070092 }
93
Mindy Pereirade3e74a2012-07-24 09:43:10 -070094 public int getSwipeAction() {
95 return mSwipeAction;
96 }
97
Mindy Pereira866d3192012-03-26 09:50:00 -070098 public void setSelectionSet(ConversationSelectionSet set) {
99 mConvSelectionSet = set;
100 }
101
Mindy Pereira06642fa2012-07-12 16:23:27 -0700102 public void setCurrentFolder(Folder folder) {
103 mFolder = folder;
104 }
105
Mindy Pereira866d3192012-03-26 09:50:00 -0700106 @Override
107 public ConversationSelectionSet getSelectionSet() {
108 return mConvSelectionSet;
109 }
110
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700111 @Override
112 public boolean onInterceptTouchEvent(MotionEvent ev) {
Mindy Pereiraff9aff32012-04-03 15:40:05 -0700113 if (mEnableSwipe) {
Mindy Pereira00ffece2012-07-27 08:49:56 -0700114 return mSwipeHelper.onInterceptTouchEvent(ev) || super.onInterceptTouchEvent(ev);
Mindy Pereira19bf5f52012-03-20 10:28:53 -0700115 } else {
116 return super.onInterceptTouchEvent(ev);
117 }
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700118 }
119
120 @Override
121 public boolean onTouchEvent(MotionEvent ev) {
Mindy Pereiraff9aff32012-04-03 15:40:05 -0700122 if (mEnableSwipe) {
Mindy Pereira19bf5f52012-03-20 10:28:53 -0700123 return mSwipeHelper.onTouchEvent(ev) || super.onTouchEvent(ev);
124 } else {
125 return super.onTouchEvent(ev);
126 }
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700127 }
128
129 @Override
130 public View getChildAtPosition(MotionEvent ev) {
131 // find the view under the pointer, accounting for GONE views
132 final int count = getChildCount();
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700133 int touchY = (int) ev.getY();
134 int childIdx = 0;
135 View slidingChild;
136 for (; childIdx < count; childIdx++) {
137 slidingChild = getChildAt(childIdx);
138 if (slidingChild.getVisibility() == GONE) {
139 continue;
140 }
Mindy Pereira866d3192012-03-26 09:50:00 -0700141 if (touchY >= slidingChild.getTop() && touchY <= slidingChild.getBottom()) {
Mindy Pereira1ef988f2012-07-24 12:06:03 -0700142 if (slidingChild instanceof SwipeableConversationItemView) {
143 return ((SwipeableConversationItemView) slidingChild).getSwipeableItemView();
144 }
Mindy Pereira866d3192012-03-26 09:50:00 -0700145 return slidingChild;
146 }
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700147 }
148 return null;
149 }
150
151 @Override
Mindy Pereira6c72a782012-04-07 14:29:15 -0700152 public boolean canChildBeDismissed(SwipeableItemView v) {
153 View view = v.getView();
154 return view instanceof ConversationItemView || view instanceof LeaveBehindItem;
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700155 }
156
157 @Override
Mindy Pereira6c72a782012-04-07 14:29:15 -0700158 public void onChildDismissed(SwipeableItemView v) {
159 View view = v.getView();
Mindy Pereirac3180e02012-07-29 10:13:21 -0700160 if (view != null) {
161 if (view instanceof ConversationItemView) {
Mindy Pereirac6e22c72012-08-10 10:14:18 -0700162 dismissChild((ConversationItemView) view);
Mindy Pereirac3180e02012-07-29 10:13:21 -0700163 } else if (view instanceof LeaveBehindItem) {
Mindy Pereira9a604d02012-08-09 12:19:13 -0700164 ((LeaveBehindItem) view).dismiss();
Mindy Pereirac3180e02012-07-29 10:13:21 -0700165 }
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700166 }
Mindy Pereira866d3192012-03-26 09:50:00 -0700167 }
168
Mindy Pereira8937bf12012-07-23 14:05:02 -0700169 // Call this whenever a new action is taken; this forces a commit of any
170 // existing destructive actions.
171 public void commitDestructiveActions() {
Mindy Pereira0fb55182012-07-24 08:22:30 -0700172 final AnimatedAdapter adapter = getAnimatedAdapter();
Mindy Pereira8937bf12012-07-23 14:05:02 -0700173 if (adapter != null) {
174 adapter.commitLeaveBehindItems();
175 }
176 }
177
Mindy Pereirac6e22c72012-08-10 10:14:18 -0700178 private void dismissChild(final ConversationItemView target) {
Mindy Pereira6c72a782012-04-07 14:29:15 -0700179 final Context context = getContext();
Mindy Pereirad33674992012-06-25 16:26:30 -0700180 final ToastBarOperation undoOp;
Mindy Pereira6c72a782012-04-07 14:29:15 -0700181
Mindy Pereirac6e22c72012-08-10 10:14:18 -0700182 undoOp = new ToastBarOperation(1, mSwipeAction, ToastBarOperation.UNDO);
Mindy Pereira6c72a782012-04-07 14:29:15 -0700183 Conversation conv = target.getConversation();
Mindy Pereira13e62432012-08-10 17:52:05 -0700184 target.getConversation().position = findConversation(target, conv);
Mindy Pereira0fb55182012-07-24 08:22:30 -0700185 final AnimatedAdapter adapter = getAnimatedAdapter();
Mindy Pereira09f1ae92012-07-18 08:28:46 -0700186 if (adapter == null) {
187 return;
188 }
Mindy Pereira6c72a782012-04-07 14:29:15 -0700189 adapter.setupLeaveBehind(conv, undoOp, conv.position);
Mindy Pereira00ffece2012-07-27 08:49:56 -0700190 ConversationCursor cc = (ConversationCursor) adapter.getCursor();
Mindy Pereira6c72a782012-04-07 14:29:15 -0700191 switch (mSwipeAction) {
Mindy Pereira01f30502012-08-14 10:30:51 -0700192 case R.id.remove_folder:
Mindy Pereira8db7e402012-07-13 10:32:47 -0700193 FolderOperation folderOp = new FolderOperation(mFolder, false);
194 HashMap<Uri, Folder> targetFolders = Folder
Mindy Pereira68f83842012-07-27 09:43:31 -0700195 .hashMapForFolders(conv.getRawFolders());
Mindy Pereira8db7e402012-07-13 10:32:47 -0700196 targetFolders.remove(folderOp.mFolder.uri);
Mindy Pereira85c4a772012-07-30 10:47:26 -0700197 conv.setRawFolders(Folder.getSerializedFolderString(targetFolders.values()));
Mindy Pereira8db7e402012-07-13 10:32:47 -0700198 cc.mostlyDestructiveUpdate(context, Conversation.listOf(conv),
Mindy Pereira85c4a772012-07-30 10:47:26 -0700199 Conversation.UPDATE_FOLDER_COLUMN, conv.getRawFoldersString());
Mindy Pereira06642fa2012-07-12 16:23:27 -0700200 break;
Mindy Pereira6c72a782012-04-07 14:29:15 -0700201 case R.id.archive:
Mindy Pereiraa8ead902012-07-12 12:06:51 -0700202 cc.mostlyArchive(context, Conversation.listOf(conv));
Mindy Pereira6c72a782012-04-07 14:29:15 -0700203 break;
204 case R.id.delete:
Mindy Pereiraa8ead902012-07-12 12:06:51 -0700205 cc.mostlyDelete(context, Conversation.listOf(conv));
Mindy Pereira6c72a782012-04-07 14:29:15 -0700206 break;
207 }
208 adapter.notifyDataSetChanged();
Mindy Pereiraa8ead902012-07-12 12:06:51 -0700209 if (mConvSelectionSet != null && !mConvSelectionSet.isEmpty()
210 && mConvSelectionSet.contains(conv)) {
211 mConvSelectionSet.toggle(null, conv);
Mindy Pereira6c72a782012-04-07 14:29:15 -0700212 }
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700213 }
214
215 @Override
216 public void onBeginDrag(View v) {
217 // We do this so the underlying ScrollView knows that it won't get
218 // the chance to intercept events anymore
219 requestDisallowInterceptTouchEvent(true);
Mindy Pereira1ef988f2012-07-24 12:06:03 -0700220 SwipeableConversationItemView view = null;
221 if (v instanceof ConversationItemView) {
Mindy Pereira00ffece2012-07-27 08:49:56 -0700222 view = (SwipeableConversationItemView) v.getParent();
Mindy Pereira1ef988f2012-07-24 12:06:03 -0700223 }
224 if (view != null) {
225 view.addBackground(getContext(), getSwipeActionText());
226 view.setBackgroundVisibility(View.VISIBLE);
227 }
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700228 }
229
230 @Override
Mindy Pereira6c72a782012-04-07 14:29:15 -0700231 public void onDragCancelled(SwipeableItemView v) {
Mindy Pereira709a1742012-07-26 09:22:04 -0700232 SwipeableConversationItemView view = null;
233 if (v instanceof ConversationItemView) {
Mindy Pereira00ffece2012-07-27 08:49:56 -0700234 view = (SwipeableConversationItemView) ((View) v).getParent();
Mindy Pereira709a1742012-07-26 09:22:04 -0700235 }
236 if (view != null) {
237 view.removeBackground();
238 }
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700239 }
240
Mindy Pereira07118a02012-04-02 16:35:01 -0700241 /**
242 * Archive items using the swipe away animation before shrinking them away.
243 */
Mindy Pereirac79aec72012-08-08 14:04:06 -0700244 public void destroyItems(final ArrayList<ConversationItemView> views,
Mindy Pereira68f83842012-07-27 09:43:31 -0700245 final DestructiveAction listener) {
Mindy Pereira07118a02012-04-02 16:35:01 -0700246 if (views == null || views.size() == 0) {
247 return;
248 }
Mindy Pereirac79aec72012-08-08 14:04:06 -0700249 // Need to find the items in the LIST!
Mindy Pereira07118a02012-04-02 16:35:01 -0700250 final ArrayList<Conversation> conversations = new ArrayList<Conversation>();
251 for (ConversationItemView view : views) {
Mindy Pereirade3e74a2012-07-24 09:43:10 -0700252 Conversation conv = view.getConversation();
Mindy Pereira067ef972012-08-08 14:04:06 -0700253 conv.position = findConversation(view, conv);
Mindy Pereirade3e74a2012-07-24 09:43:10 -0700254 conversations.add(conv);
Mindy Pereira07118a02012-04-02 16:35:01 -0700255 }
Mindy Pereirac79aec72012-08-08 14:04:06 -0700256 AnimatedAdapter adapter = getAnimatedAdapter();
257 if (adapter != null) {
Mindy Pereira067ef972012-08-08 14:04:06 -0700258 adapter.swipeDelete(conversations, listener);
Mindy Pereirac79aec72012-08-08 14:04:06 -0700259 }
Mindy Pereira07118a02012-04-02 16:35:01 -0700260 }
261
Mindy Pereira067ef972012-08-08 14:04:06 -0700262 private int findConversation(ConversationItemView view, Conversation conv) {
263 int position = conv.position;
264 long convId = conv.id;
265 try {
266 if (position == INVALID_POSITION) {
267 position = getPositionForView(view);
268 }
269 } catch (Exception e) {
270 position = INVALID_POSITION;
271 LogUtils.w(LOG_TAG, "Exception finding position; using alternate strategy");
272 }
273 if (position == INVALID_POSITION) {
274 // Try the other way!
275 Conversation foundConv;
276 long foundId;
277 for (int i = 0; i < getChildCount(); i++) {
278 View child = getChildAt(i);
279 if (child instanceof SwipeableConversationItemView) {
280 foundConv = ((SwipeableConversationItemView) child).getSwipeableItemView()
281 .getConversation();
282 foundId = foundConv.id;
283 if (foundId == convId) {
284 position = i;
285 break;
286 }
287 }
288 }
289 }
290 return position;
291 }
292
Mindy Pereira0fb55182012-07-24 08:22:30 -0700293 private AnimatedAdapter getAnimatedAdapter() {
294 return (AnimatedAdapter) getAdapter();
295 }
296
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700297 public interface SwipeCompleteListener {
298 public void onSwipeComplete(Collection<Conversation> conversations);
299 }
Mindy Pereira8937bf12012-07-23 14:05:02 -0700300
301 @Override
302 public boolean performItemClick(View view, int pos, long id) {
Mindy Pereiradd9d3a12012-08-06 16:35:36 -0700303 boolean handled = super.performItemClick(view, pos, id);
Mindy Pereira00ffece2012-07-27 08:49:56 -0700304 // Commit any existing destructive actions when the user selects a
305 // conversation to view.
Mindy Pereira8937bf12012-07-23 14:05:02 -0700306 commitDestructiveActions();
Mindy Pereiradd9d3a12012-08-06 16:35:36 -0700307 return handled;
Mindy Pereira8937bf12012-07-23 14:05:02 -0700308 }
Mindy Pereira1ef988f2012-07-24 12:06:03 -0700309
310 /**
311 * Get the text resource corresponding to the result of a swipe.
312 */
Mindy Pereirabcd784c2012-08-10 09:53:24 -0700313 public String getSwipeActionText() {
314 Resources res = getContext().getResources();
Mindy Pereira01f30502012-08-14 10:30:51 -0700315 if (mSwipeAction == R.id.remove_folder) {
Mindy Pereirabcd784c2012-08-10 09:53:24 -0700316 return res.getString(R.string.remove_folder, mFolder.name);
Mindy Pereira1ef988f2012-07-24 12:06:03 -0700317 }
Mindy Pereirabcd784c2012-08-10 09:53:24 -0700318 return res.getString(mSwipeAction == R.id.archive ? R.string.archive : R.string.delete);
Mindy Pereira1ef988f2012-07-24 12:06:03 -0700319 }
Mindy Pereira00ffece2012-07-27 08:49:56 -0700320}