Merge pull request #1104 from BrianBlade/feature/swipe_out_conversation

Daniel Gultsch created

Enable end-conversation by swipe gesture

Change summary

build.gradle                                                             |   1 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |   1 
src/main/java/eu/siacs/conversations/ui/ConversationActivity.java        | 103 
src/main/res/layout-w960dp/fragment_conversations_overview.xml           |   2 
src/main/res/layout/conversation_list_row.xml                            | 138 
src/main/res/layout/fragment_conversations_overview.xml                  |   2 
src/main/res/values-de/strings.xml                                       |   2 
src/main/res/values-v21/dimens.xml                                       |   4 
src/main/res/values/strings.xml                                          |   3 
9 files changed, 185 insertions(+), 71 deletions(-)

Detailed changes

build.gradle ๐Ÿ”—

@@ -34,6 +34,7 @@ dependencies {
 	compile 'com.google.zxing:core:3.1.0'
 	compile 'com.google.zxing:android-integration:3.1.0'
 	compile 'de.measite.minidns:minidns:0.1.3'
+	compile 'de.timroes.android:EnhancedListView:0.3.4'
 }
 
 android {

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java ๐Ÿ”—

@@ -1129,6 +1129,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 	}
 
 	public void archiveConversation(Conversation conversation) {
+		getNotificationService().clear(conversation);
 		conversation.setStatus(Conversation.STATUS_ARCHIVED);
 		conversation.setNextEncryption(-1);
 		synchronized (this.conversations) {

src/main/java/eu/siacs/conversations/ui/ConversationActivity.java ๐Ÿ”—

@@ -22,12 +22,12 @@ import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ArrayAdapter;
 import android.widget.CheckBox;
-import android.widget.ListView;
 import android.widget.PopupMenu;
 import android.widget.PopupMenu.OnMenuItemClickListener;
 import android.widget.Toast;
 
 import net.java.otr4j.session.SessionStatus;
+import de.timroes.android.listview.EnhancedListView;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -76,8 +76,9 @@ public class ConversationActivity extends XmppActivity
 	private View mContentView;
 
 	private List<Conversation> conversationList = new ArrayList<>();
+	private Conversation swipedConversation = null;
 	private Conversation mSelectedConversation = null;
-	private ListView listView;
+	private EnhancedListView listView;
 	private ConversationFragment mConversationFragment;
 
 	private ArrayAdapter<Conversation> listAdapter;
@@ -156,7 +157,7 @@ public class ConversationActivity extends XmppActivity
 		transaction.replace(R.id.selected_conversation, this.mConversationFragment, "conversation");
 		transaction.commit();
 
-		listView = (ListView) findViewById(R.id.list);
+		listView = (EnhancedListView) findViewById(R.id.list);
 		this.listAdapter = new ConversationAdapter(this, conversationList);
 		listView.setAdapter(this.listAdapter);
 
@@ -178,6 +179,73 @@ public class ConversationActivity extends XmppActivity
 				openConversation();
 			}
 		});
+
+		listView.setDismissCallback(new EnhancedListView.OnDismissCallback() {
+
+			@Override
+			public EnhancedListView.Undoable onDismiss(final EnhancedListView enhancedListView, final int position) {
+
+				final int index = listView.getFirstVisiblePosition();
+				View v = listView.getChildAt(0);
+				final int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop());
+
+				swipedConversation = listAdapter.getItem(position);
+				listAdapter.remove(swipedConversation);
+				swipedConversation.markRead();
+				xmppConnectionService.getNotificationService().clear(swipedConversation);
+
+				final boolean formerlySelected = (getSelectedConversation() == swipedConversation);
+				if (position == 0 && listAdapter.getCount() == 0) {
+					endConversation(swipedConversation, false, true);
+					return null;
+				} else if (formerlySelected) {
+					setSelectedConversation(listAdapter.getItem(0));
+					ConversationActivity.this.mConversationFragment
+							.reInit(getSelectedConversation());
+				}
+
+				return new EnhancedListView.Undoable() {
+
+					@Override
+					public void undo() {
+						listAdapter.insert(swipedConversation, position);
+						if (formerlySelected) {
+							setSelectedConversation(swipedConversation);
+							ConversationActivity.this.mConversationFragment
+									.reInit(getSelectedConversation());
+						}
+						swipedConversation = null;
+						listView.setSelectionFromTop(index + (listView.getChildCount() < position ? 1 : 0), top);
+					}
+
+					@Override
+					public void discard() {
+						if (!swipedConversation.isRead()
+								&& swipedConversation.getMode() == Conversation.MODE_SINGLE) {
+							swipedConversation = null;
+							return;
+						}
+						endConversation(swipedConversation, false, false);
+						swipedConversation = null;
+					}
+
+					@Override
+					public String getTitle() {
+						if (swipedConversation.getMode() == Conversation.MODE_MULTI) {
+							return getResources().getString(R.string.title_undo_swipe_out_muc);
+						} else {
+							return getResources().getString(R.string.title_undo_swipe_out_conversation);
+						}
+					}
+				};
+			}
+		});
+		listView.enableSwipeToDismiss();
+		listView.setSwipingLayout(R.id.swipeable_item);
+		listView.setUndoStyle(EnhancedListView.UndoStyle.SINGLE_POPUP);
+		listView.setUndoHideDelay(3000);
+		listView.setRequireTouchBeforeDismiss(false);
+
 		mContentView = findViewById(R.id.content_view_spl);
 		if (mContentView == null) {
 			mContentView = findViewById(R.id.content_view_ll);
@@ -204,6 +272,7 @@ public class ConversationActivity extends XmppActivity
 
 				@Override
 				public void onPanelClosed(View arg0) {
+					listView.discardUndo();
 					openConversation();
 				}
 
@@ -485,13 +554,21 @@ public class ConversationActivity extends XmppActivity
 	}
 
 	public void endConversation(Conversation conversation) {
-		showConversationsOverview();
+		endConversation(conversation, true, true);
+	}
+
+	public void endConversation(Conversation conversation, boolean showOverview, boolean reinit) {
+		if (showOverview) {
+			showConversationsOverview();
+		}
 		xmppConnectionService.archiveConversation(conversation);
-		if (conversationList.size() > 0) {
-			setSelectedConversation(conversationList.get(0));
-			this.mConversationFragment.reInit(getSelectedConversation());
-		} else {
-			setSelectedConversation(null);
+		if (reinit) {
+			if (conversationList.size() > 0) {
+				setSelectedConversation(conversationList.get(0));
+				this.mConversationFragment.reInit(getSelectedConversation());
+			} else {
+				setSelectedConversation(null);
+			}
 		}
 	}
 
@@ -744,6 +821,7 @@ public class ConversationActivity extends XmppActivity
 
 	@Override
 	public void onPause() {
+		listView.discardUndo();
 		super.onPause();
 		this.mActivityPaused = true;
 		if (this.xmppConnectionServiceBound) {
@@ -1013,6 +1091,13 @@ public class ConversationActivity extends XmppActivity
 	public void updateConversationList() {
 		xmppConnectionService
 			.populateWithOrderedConversations(conversationList);
+		if (swipedConversation != null) {
+			if (swipedConversation.isRead()) {
+				conversationList.remove(swipedConversation);
+			} else {
+				listView.discardUndo();
+			}
+		}
 		listAdapter.notifyDataSetChanged();
 	}
 

src/main/res/layout/conversation_list_row.xml ๐Ÿ”—

@@ -1,68 +1,86 @@
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:padding="8dp" >
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+	android:layout_width="fill_parent"
+	android:layout_height="wrap_content"
+	android:descendantFocusability="blocksDescendants">
 
-    <ImageView
-        android:id="@+id/conversation_image"
-        android:layout_width="56dp"
-        android:layout_height="56dp"
-        android:layout_alignParentLeft="true"
-        android:scaleType="centerCrop" />
+	<View
+		android:layout_width="fill_parent"
+		android:layout_height="fill_parent"
+		android:background="@color/divider"/>
 
-    <RelativeLayout
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:layout_centerVertical="true"
-        android:layout_toRightOf="@+id/conversation_image"
-        android:paddingLeft="8dp" >
+	<FrameLayout
+		android:id="@+id/swipeable_item"
+		android:layout_width="fill_parent"
+		android:layout_height="fill_parent"
+		android:background="@color/primarybackground">
 
-        <TextView
-            android:id="@+id/conversation_name"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignLeft="@+id/conversation_lastwrapper"
-            android:layout_toLeftOf="@+id/conversation_lastupdate"
-            android:singleLine="true"
-            android:textColor="@color/primarytext"
-            android:textSize="?attr/TextSizeHeadline"
-            android:typeface="sans" />
+	<RelativeLayout
+		android:layout_width="fill_parent"
+		android:layout_height="wrap_content"
+		android:background="?android:selectableItemBackground"
+		android:orientation="horizontal"
+		android:padding="8dp" >
 
-        <LinearLayout
-            android:id="@+id/conversation_lastwrapper"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:layout_below="@id/conversation_name"
-            android:orientation="vertical"
-            android:paddingTop="3dp" >
+		<ImageView
+			android:id="@+id/conversation_image"
+			android:layout_width="56dp"
+			android:layout_height="56dp"
+			android:layout_alignParentLeft="true"
+			android:scaleType="centerCrop" />
 
-            <TextView
-                android:id="@+id/conversation_lastmsg"
-                android:layout_width="fill_parent"
-                android:layout_height="wrap_content"
-                android:scrollHorizontally="false"
-                android:singleLine="true"
-                android:textColor="@color/primarytext"
-                android:textSize="?attr/TextSizeBody" />
+		<RelativeLayout
+			android:layout_width="fill_parent"
+			android:layout_height="wrap_content"
+			android:layout_centerVertical="true"
+			android:layout_toRightOf="@+id/conversation_image"
+			android:paddingLeft="8dp" >
 
-            <ImageView
-                android:id="@+id/conversation_lastimage"
-                android:layout_width="fill_parent"
-                android:layout_height="36dp"
-                android:background="@color/primarytext"
-                android:scaleType="centerCrop" />
-        </LinearLayout>
+			<TextView
+				android:id="@+id/conversation_name"
+				android:layout_width="wrap_content"
+				android:layout_height="wrap_content"
+				android:layout_alignLeft="@+id/conversation_lastwrapper"
+				android:layout_toLeftOf="@+id/conversation_lastupdate"
+				android:singleLine="true"
+				android:textColor="@color/primarytext"
+				android:textSize="?attr/TextSizeHeadline"
+				android:typeface="sans" />
 
-        <TextView
-            android:id="@+id/conversation_lastupdate"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignBaseline="@+id/conversation_name"
-            android:layout_alignParentRight="true"
-            android:gravity="right"
-            android:textColor="@color/secondarytext"
-            android:textSize="?attr/TextSizeInfo" />
-    </RelativeLayout>
+			<LinearLayout
+				android:id="@+id/conversation_lastwrapper"
+				android:layout_width="fill_parent"
+				android:layout_height="wrap_content"
+				android:layout_below="@id/conversation_name"
+				android:orientation="vertical"
+				android:paddingTop="3dp" >
 
-</RelativeLayout>
+				<TextView
+					android:id="@+id/conversation_lastmsg"
+					android:layout_width="fill_parent"
+					android:layout_height="wrap_content"
+					android:scrollHorizontally="false"
+					android:singleLine="true"
+					android:textColor="@color/primarytext"
+					android:textSize="?attr/TextSizeBody" />
+
+				<ImageView
+					android:id="@+id/conversation_lastimage"
+					android:layout_width="fill_parent"
+					android:layout_height="36dp"
+					android:background="@color/primarytext"
+					android:scaleType="centerCrop" />
+			</LinearLayout>
+
+			<TextView
+				android:id="@+id/conversation_lastupdate"
+				android:layout_width="wrap_content"
+				android:layout_height="wrap_content"
+				android:layout_alignBaseline="@+id/conversation_name"
+				android:layout_alignParentRight="true"
+				android:gravity="right"
+				android:textColor="@color/secondarytext"
+				android:textSize="?attr/TextSizeInfo" />
+		</RelativeLayout>
+	</RelativeLayout>
+	</FrameLayout>
+</FrameLayout>

src/main/res/values-de/strings.xml ๐Ÿ”—

@@ -427,6 +427,8 @@
   <string name="no_application_found_to_display_location">Keine App fรผr die Standort-Anzeige gefunden</string>
   <string name="location">Standort</string>
   <string name="received_location">Standort empfangen</string>
+  <string name="title_undo_swipe_out_conversation">Unterhaltung beendet</string>
+  <string name="title_undo_swipe_out_muc">Konferenz verlassen</string>
   <plurals name="select_contact">
     <item quantity="one">%d Kontakt ausgewรคhlt</item>
     <item quantity="other">%d Kontakte ausgewรคhlt</item>

src/main/res/values/strings.xml ๐Ÿ”—

@@ -296,6 +296,7 @@
             \n\nhttps://developer.android.com/tools/support-library\n(Apache License, Version 2.0)
             \n\nhttps://github.com/zxing/zxing\n(Apache License, Version 2.0)
             \n\nhttps://github.com/google/material-design-icons\n(CC BY 4.0)
+            \n\nhttps://github.com/timroes/EnhancedListView\n(Apache License, Version 2.0)
     </string>
     <string name="title_pref_quiet_hours">Quiet Hours</string>
     <string name="title_pref_quiet_hours_start_time">Start time</string>
@@ -454,6 +455,8 @@
     <string name="no_application_found_to_display_location">No application found to display location</string>
     <string name="location">Location</string>
     <string name="received_location">Received location</string>
+	<string name="title_undo_swipe_out_conversation">Conversation closed</string>
+	<string name="title_undo_swipe_out_muc">Left conference</string>
 	<plurals name="select_contact">
 		<item quantity="one">Select %d contact</item>
 		<item quantity="other">Select %d contacts</item>