migrate the RecyclerView

Ye feng created

Change summary

build.gradle                                                                          |   1 
libs/EnhancedListView/build.gradle                                                    |  33 
libs/EnhancedListView/src/main/AndroidManifest.xml                                    |   6 
libs/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java | 969 
libs/EnhancedListView/src/main/res/anim/elv_popup_hide.xml                            |   7 
libs/EnhancedListView/src/main/res/anim/elv_popup_show.xml                            |   7 
libs/EnhancedListView/src/main/res/drawable-hdpi/elv_ic_action_undo.png               |   0 
libs/EnhancedListView/src/main/res/drawable-hdpi/elv_toast_frame.9.png                |   0 
libs/EnhancedListView/src/main/res/drawable-ldpi/elv_ic_action_undo.png               |   0 
libs/EnhancedListView/src/main/res/drawable-ldpi/elv_toast_frame.9.png                |   0 
libs/EnhancedListView/src/main/res/drawable-mdpi/elv_ic_action_undo.png               |   0 
libs/EnhancedListView/src/main/res/drawable-mdpi/elv_toast_frame.9.png                |   0 
libs/EnhancedListView/src/main/res/drawable-xhdpi/elv_ic_action_undo.png              |   0 
libs/EnhancedListView/src/main/res/drawable-xhdpi/elv_toast_frame.9.png               |   0 
libs/EnhancedListView/src/main/res/drawable-xxhdpi/elv_ic_action_undo.png             |   0 
libs/EnhancedListView/src/main/res/drawable-xxhdpi/elv_toast_frame.9.png              |   0 
libs/EnhancedListView/src/main/res/drawable/elv_popup_bg.xml                          |   5 
libs/EnhancedListView/src/main/res/drawable/elv_undo_btn_bg.xml                       |   6 
libs/EnhancedListView/src/main/res/drawable/elv_undo_btn_bg_focused.xml               |   5 
libs/EnhancedListView/src/main/res/drawable/elv_undo_btn_bg_pressed.xml               |   5 
libs/EnhancedListView/src/main/res/layout-v19/elv_undo_popup.xml                      |  43 
libs/EnhancedListView/src/main/res/layout/elv_undo_popup.xml                          |  35 
libs/EnhancedListView/src/main/res/values-v19/colors.xml                              |   4 
libs/EnhancedListView/src/main/res/values/colors.xml                                  |  10 
libs/EnhancedListView/src/main/res/values/dimens.xml                                  |   7 
libs/EnhancedListView/src/main/res/values/strings.xml                                 |   7 
libs/EnhancedListView/src/main/res/values/styles.xml                                  |   7 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java                     |  57 
src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java            | 190 
src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java                        |  18 
src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java              |  98 
src/main/java/eu/siacs/conversations/ui/util/PendingActionHelper.java                 |  29 
src/main/res/layout/activity_share_with.xml                                           |   2 
src/main/res/layout/conversation_list_row.xml                                         |   8 
src/main/res/layout/fragment_conversations_overview.xml                               |  13 
src/main/res/values/strings.xml                                                       |   1 
36 files changed, 250 insertions(+), 1,323 deletions(-)

Detailed changes

build.gradle 🔗

@@ -33,7 +33,6 @@ ext {
 }
 
 dependencies {
-    implementation project(':libs:EnhancedListView')
     playstoreImplementation 'com.google.android.gms:play-services-gcm:12.0.1'
     implementation 'org.sufficientlysecure:openpgp-api:10.0'
     implementation 'com.soundcloud.android:android-crop:1.0.1@aar'

libs/EnhancedListView/build.gradle 🔗

@@ -1,33 +0,0 @@
-apply plugin: 'com.android.library'
-
-repositories {
-    mavenCentral()
-    google()
-}
-
-dependencies {
-    implementation 'com.android.support:support-v4:27.0.2'
-    implementation 'com.nineoldandroids:library:2.4.0'
-}
-
-android {
-    compileSdkVersion 27
-    buildToolsVersion "27.0.3"
-
-    defaultConfig {
-        minSdkVersion 14
-        targetSdkVersion 25
-        versionName "0.3.4"
-        versionCode 9
-    }
-
-    lintOptions {
-        abortOnError false
-    }
-}
-
-apply plugin: 'maven'
-apply plugin: 'signing'
-
-version = android.defaultConfig.versionName
-group = "de.timroes.android"

libs/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java 🔗

@@ -1,969 +0,0 @@
-/*
- * Copyright 2012 - 2013 Roman Nurik, Jake Wharton, Tim Roes
- *
- * 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 de.timroes.android.listview;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Message;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.widget.AbsListView;
-import android.widget.Button;
-import android.widget.ListView;
-import android.widget.PopupWindow;
-import android.widget.TextView;
-
-import com.nineoldandroids.animation.Animator;
-import com.nineoldandroids.animation.AnimatorListenerAdapter;
-import com.nineoldandroids.animation.ValueAnimator;
-import com.nineoldandroids.view.ViewHelper;
-import com.nineoldandroids.view.ViewPropertyAnimator;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-/**
- * A {@link android.widget.ListView} offering enhanced features like Swipe To Dismiss and an
- * undo functionality. See the documentation on GitHub for more information.
- *
- * @author Tim Roes <mail@timroes.de>
- */
-public class EnhancedListView extends ListView {
-
-    /**
-     * Defines the style in which <i>undos</i> should be displayed and handled in the list.
-     * Pass this to {@link #setUndoStyle(de.timroes.android.listview.EnhancedListView.UndoStyle)}
-     * to change the default behavior from {@link #SINGLE_POPUP}.
-     */
-    public enum UndoStyle {
-
-        /**
-         * Shows a popup window, that allows the user to undo the last
-         * dismiss. If another element is deleted, the undo popup will undo that deletion.
-         * The user is only able to undo the last deletion.
-         */
-        SINGLE_POPUP,
-
-        /**
-         * Shows a popup window, that allows the user to undo the last dismiss.
-         * If another item is deleted, this will be added to the chain of undos. So pressing
-         * undo will undo the last deletion, pressing it again will undo the deletion before that,
-         * and so on. As soon as the popup vanished (e.g. because {@link #setUndoHideDelay(int) autoHideDelay}
-         * is over) all saved undos will be discarded.
-         */
-        MULTILEVEL_POPUP,
-
-        /**
-         * Shows a popup window, that allows the user to undo the last dismisses.
-         * If another item is deleted, while there is still an undo popup visible, the label
-         * of the button changes to <i>Undo all</i> and a press on the button, will discard
-         * all stored undos. As soon as the popup vanished (e.g. because {@link #setUndoHideDelay(int) autoHideDelay}
-         * is over) all saved undos will be discarded.
-         */
-        COLLAPSED_POPUP
-
-    }
-
-    /**
-     * Defines the direction in which list items can be swiped out to delete them.
-     * Use {@link #setSwipeDirection(de.timroes.android.listview.EnhancedListView.SwipeDirection)}
-     * to change the default behavior.
-     * <p>
-     * <b>Note:</b> This method requires the <i>Swipe to Dismiss</i> feature enabled. Use
-     * {@link #enableSwipeToDismiss()}
-     * to enable the feature.
-     */
-    public enum SwipeDirection {
-
-        /**
-         * The user can swipe each item into both directions (left and right) to delete it.
-         */
-        BOTH,
-
-        /**
-         * The user can only swipe the items to the beginning of the item to
-         * delete it. The start of an item is in Left-To-Right languages the left
-         * side and in Right-To-Left languages the right side. Before API level
-         * 17 this is always the left side.
-         */
-        START,
-
-        /**
-         * The user can only swipe the items to the end of the item to delete it.
-         * This is in Left-To-Right languages the right side in Right-To-Left
-         * languages the left side. Before API level 17 this will always be the
-         * right side.
-         */
-        END
-
-    }
-
-    /**
-     * The callback interface used by {@link #setShouldSwipeCallback(EnhancedListView.OnShouldSwipeCallback)}
-     * to inform its client that a list item is going to be swiped and check whether is
-     * should or not. Implement this to prevent some items from be swiped.
-     */
-    public interface OnShouldSwipeCallback {
-
-        /**
-         * Called when the user is swiping an item from the list.
-         * <p>
-         * If the user should get the possibility to swipe the item, return true.
-         * Otherwise, return false to disable swiping for this item.
-         *
-         * @param listView The {@link EnhancedListView} the item is wiping from.
-         * @param position The position of the item to swipe in your adapter.
-         * @return Whether the item should be swiped or not.
-         */
-        boolean onShouldSwipe(EnhancedListView listView, int position);
-
-    }
-
-    /**
-     * The callback interface used by {@link #setDismissCallback(EnhancedListView.OnDismissCallback)}
-     * to inform its client about a successful dismissal of one or more list item positions.
-     * Implement this to remove items from your adapter, that has been swiped from the list.
-     */
-    public interface OnDismissCallback {
-
-        /**
-         * Called when the user has deleted an item from the list. The item has been deleted from
-         * the {@code listView} at {@code position}. Delete this item from your adapter.
-         * <p>
-         * Don't return from this method, before your item has been deleted from the adapter, meaning
-         * if you delete the item in another thread, you have to make sure, you don't return from
-         * this method, before the item has been deleted. Since the way how you delete your item
-         * depends on your data and adapter, the {@link de.timroes.android.listview.EnhancedListView}
-         * cannot handle that synchronizing for you. If you return from this method before you removed
-         * the view from the adapter, you will most likely get errors like exceptions and flashing
-         * items in the list.
-         * <p>
-         * If the user should get the possibility to undo this deletion, return an implementation
-         * of {@link de.timroes.android.listview.EnhancedListView.Undoable} from this method.
-         * If you return {@code null} no undo will be possible. You are free to return an {@code Undoable}
-         * for some items, and {@code null} for others, though it might be a horrible user experience.
-         *
-         * @param listView The {@link EnhancedListView} the item has been deleted from.
-         * @param position The position of the item to delete from your adapter.
-         * @return An {@link de.timroes.android.listview.EnhancedListView.Undoable}, if you want
-         *      to give the user the possibility to undo the deletion.
-         */
-        Undoable onDismiss(EnhancedListView listView, int position);
-
-    }
-
-    /**
-     * Extend this abstract class and return it from
-     * {@link EnhancedListView.OnDismissCallback#onDismiss(EnhancedListView, int)}
-     * to let the user undo the deletion you've done with your {@link EnhancedListView.OnDismissCallback}.
-     * You have at least to implement the {@link #undo()} method, and can override {@link #discard()}
-     * and {@link #getTitle()} to offer more functionality. See the README file for example implementations.
-     */
-    public abstract static class Undoable {
-
-        /**
-         * This method must undo the deletion you've done in
-         * {@link EnhancedListView.OnDismissCallback#onDismiss(EnhancedListView, int)} and reinsert
-         * the element into the adapter.
-         * <p>
-         * In the most implementations, you will only remove the list item from your adapter
-         * in the {@code onDismiss} method and delete it from the database (or your permanent
-         * storage) in {@link #discard()}. In that case you only need to reinsert the item
-         * to the adapter.
-         */
-        public abstract void undo();
-
-        /**
-         * Returns the individual undo message for this undo. This will be displayed in the undo
-         * window, beside the undo button. The default implementation returns {@code null},
-         * what will lead in a default message to be displayed in the undo window.
-         * Don't call the super method, when overriding this method.
-         *
-         * @return The title for a special string.
-         */
-        public String getTitle() {
-            return null;
-        }
-
-        /**
-         * Discard the undo, meaning the user has no longer the possibility to undo the deletion.
-         * Implement this, to finally delete your stuff from permanent storages like databases
-         * (whereas in {@link de.timroes.android.listview.EnhancedListView.OnDismissCallback#onKeyDown(int, android.view.KeyEvent)}
-         * you should only remove it from the list adapter).
-         */
-        public void discard() { }
-
-    }
-
-    private class PendingDismissData implements Comparable<PendingDismissData> {
-
-        public int position;
-        /**
-         * The view that should get swiped out.
-         */
-        public View view;
-        /**
-         * The whole list item view.
-         */
-        public View childView;
-
-        PendingDismissData(int position, View view, View childView) {
-            this.position = position;
-            this.view = view;
-            this.childView = childView;
-        }
-
-        @Override
-        public int compareTo(PendingDismissData other) {
-            // Sort by descending position
-            return other.position - position;
-        }
-
-    }
-
-    private class UndoClickListener implements OnClickListener {
-
-        /**
-         * Called when a view has been clicked.
-         *
-         * @param v The view that was clicked.
-         */
-        @Override
-        public void onClick(View v) {
-            if(!mUndoActions.isEmpty()) {
-                switch(mUndoStyle) {
-                    case SINGLE_POPUP:
-                        mUndoActions.get(0).undo();
-                        mUndoActions.clear();
-                        break;
-                    case COLLAPSED_POPUP:
-                        Collections.reverse(mUndoActions);
-                        for(Undoable undo : mUndoActions) {
-                            undo.undo();
-                        }
-                        mUndoActions.clear();
-                        break;
-                    case MULTILEVEL_POPUP:
-                        mUndoActions.get(mUndoActions.size() - 1).undo();
-                        mUndoActions.remove(mUndoActions.size() - 1);
-                        break;
-                }
-            }
-
-            // Dismiss dialog or change text
-            if(mUndoActions.isEmpty()) {
-                if(mUndoPopup.isShowing()) {
-                    mUndoPopup.dismiss();
-                }
-            } else {
-                changePopupText();
-                changeButtonLabel();
-            }
-
-            mValidDelayedMsgId++;
-        }
-    }
-
-    private class HideUndoPopupHandler extends Handler {
-
-        /**
-         * Subclasses must implement this to receive messages.
-         */
-        @Override
-        public void handleMessage(Message msg) {
-            if(msg.what == mValidDelayedMsgId) {
-            	discardUndo();
-            }
-        }
-    }
-
-    // Cached ViewConfiguration and system-wide constant values
-    private float mSlop;
-    private int mMinFlingVelocity;
-    private int mMaxFlingVelocity;
-    private long mAnimationTime;
-
-    private final Object[] mAnimationLock = new Object[0];
-
-    // Swipe-To-Dismiss
-    private boolean mSwipeEnabled;
-    private OnDismissCallback mDismissCallback;
-    private OnShouldSwipeCallback mShouldSwipeCallback;
-    private UndoStyle mUndoStyle = UndoStyle.SINGLE_POPUP;
-    private boolean mTouchBeforeAutoHide = true;
-    private SwipeDirection mSwipeDirection = SwipeDirection.BOTH;
-    private int mUndoHideDelay = 5000;
-    private int mSwipingLayout;
-
-    private List<Undoable> mUndoActions = new ArrayList<Undoable>();
-    private SortedSet<PendingDismissData> mPendingDismisses = new TreeSet<PendingDismissData>();
-    private List<View> mAnimatedViews = new LinkedList<View>();
-    private int mDismissAnimationRefCount;
-
-    private boolean mSwipePaused;
-    private boolean mSwiping;
-    private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
-    private View mSwipeDownView;
-    private View mSwipeDownChild;
-    private TextView mUndoPopupTextView;
-    private VelocityTracker mVelocityTracker;
-    private float mDownX;
-    private int mDownPosition;
-    private float mScreenDensity;
-
-    private PopupWindow mUndoPopup;
-    private int mValidDelayedMsgId;
-    private Handler mHideUndoHandler = new HideUndoPopupHandler();
-    private Button mUndoButton;
-    // END Swipe-To-Dismiss
-
-    /**
-     * {@inheritDoc}
-     */
-    public EnhancedListView(Context context) {
-        super(context);
-        init(context);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public EnhancedListView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        init(context);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public EnhancedListView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        init(context);
-    }
-
-    private void init(Context ctx) {
-
-        if(isInEditMode()) {
-            // Skip initializing when in edit mode (IDE preview).
-            return;
-        }
-        ViewConfiguration vc =ViewConfiguration.get(ctx);
-        mSlop = getResources().getDimension(R.dimen.elv_touch_slop);
-		mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
-        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
-        mAnimationTime = ctx.getResources().getInteger(
-                android.R.integer.config_shortAnimTime);
-
-        // Initialize undo popup
-        LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        View undoView = inflater.inflate(R.layout.elv_undo_popup, null);
-        mUndoButton = (Button)undoView.findViewById(R.id.undo);
-        mUndoButton.setOnClickListener(new UndoClickListener());
-        mUndoButton.setOnTouchListener(new OnTouchListener() {
-            @Override
-            public boolean onTouch(View v, MotionEvent event) {
-                // If the user touches the screen invalidate the current running delay by incrementing
-                // the valid message id. So this delay won't hide the undo popup anymore
-                mValidDelayedMsgId++;
-                return false;
-            }
-        });
-        mUndoPopupTextView = (TextView)undoView.findViewById(R.id.text);
-
-        mUndoPopup = new PopupWindow(undoView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, false);
-        mUndoPopup.setAnimationStyle(R.style.elv_fade_animation);
-
-        mScreenDensity = getResources().getDisplayMetrics().density;
-        // END initialize undo popup
-
-        setOnScrollListener(makeScrollListener());
-
-    }
-
-    /**
-     * Enables the <i>Swipe to Dismiss</i> feature for this list. This allows users to swipe out
-     * an list item element to delete it from the list. Every time the user swipes out an element
-     * {@link de.timroes.android.listview.EnhancedListView.OnDismissCallback#onDismiss(EnhancedListView, int)}
-     * of the given {@link de.timroes.android.listview.EnhancedListView} will be called. To enable
-     * <i>undo</i> of the deletion, return an {@link de.timroes.android.listview.EnhancedListView.Undoable}
-     * from {@link de.timroes.android.listview.EnhancedListView.OnDismissCallback#onDismiss(EnhancedListView, int)}.
-     * Return {@code null}, if you don't want the <i>undo</i> feature enabled. Read the README file
-     * or the demo project for more detailed samples.
-     *
-     * @return The {@link de.timroes.android.listview.EnhancedListView}
-     * @throws java.lang.IllegalStateException when you haven't passed an {@link EnhancedListView.OnDismissCallback}
-     *      to {@link #setDismissCallback(EnhancedListView.OnDismissCallback)} before calling this
-     *      method.
-     */
-    public EnhancedListView enableSwipeToDismiss() {
-
-        if(mDismissCallback == null) {
-            throw new IllegalStateException("You must pass an OnDismissCallback to the list before enabling Swipe to Dismiss.");
-        }
-
-        mSwipeEnabled = true;
-
-        return this;
-    }
-
-    /**
-     * Disables the <i>Swipe to Dismiss</i> feature for this list.
-     *
-     * @return This {@link de.timroes.android.listview.EnhancedListView}
-     */
-    public EnhancedListView disableSwipeToDismiss() {
-        mSwipeEnabled = false;
-        return this;
-    }
-
-    /**
-     * Sets the callback to be called when the user dismissed an item from the list (either by
-     * swiping it out - with <i>Swipe to Dismiss</i> enabled - or by deleting it with
-     * {@link #delete(int)}). You must call this, before you call {@link #delete(int)} or
-     * {@link #enableSwipeToDismiss()} otherwise you will get an {@link java.lang.IllegalStateException}.
-     *
-     * @param dismissCallback The callback used to handle dismisses of list items.
-     * @return This {@link de.timroes.android.listview.EnhancedListView}
-     */
-    public EnhancedListView setDismissCallback(OnDismissCallback dismissCallback) {
-        mDismissCallback = dismissCallback;
-        return this;
-    }
-
-    /**
-     * Sets the callback to be called when the user is swiping an item from the list.
-     *
-     * @param shouldSwipeCallback The callback used to handle swipes of list items.
-     * @return This {@link de.timroes.android.listview.EnhancedListView}
-     */
-    public EnhancedListView setShouldSwipeCallback(OnShouldSwipeCallback shouldSwipeCallback) {
-        mShouldSwipeCallback = shouldSwipeCallback;
-        return this;
-    }
-
-    /**
-     * Sets the undo style of this list. See the javadoc of {@link de.timroes.android.listview.EnhancedListView.UndoStyle}
-     * for a detailed explanation of the different styles. The default style (if you never call this
-     * method) is {@link de.timroes.android.listview.EnhancedListView.UndoStyle#SINGLE_POPUP}.
-     *
-     * @param undoStyle The style of this listview.
-     * @return This {@link de.timroes.android.listview.EnhancedListView}
-     */
-    public EnhancedListView setUndoStyle(UndoStyle undoStyle) {
-        mUndoStyle = undoStyle;
-        return this;
-    }
-
-    /**
-     * Sets the time in milliseconds after which the undo popup automatically disappears.
-     * The countdown will start when the user touches the screen. If you want to start the countdown
-     * immediately when the popups appears, call {@link #setRequireTouchBeforeDismiss(boolean)} with
-     * {@code false}.
-     *
-     * @param hideDelay The delay in milliseconds.
-     * @return This {@link de.timroes.android.listview.EnhancedListView}
-     */
-    public EnhancedListView setUndoHideDelay(int hideDelay) {
-        mUndoHideDelay = hideDelay;
-        return this;
-    }
-
-    /**
-     * Sets whether another touch on the view is required before the popup counts down to dismiss
-     * the undo popup. By default this is set to {@code true}.
-     *
-     * @param touchBeforeDismiss Whether the screen needs to be touched before the countdown starts.
-     * @return This {@link de.timroes.android.listview.EnhancedListView}
-     *
-     * @see #setUndoHideDelay(int)
-     */
-    public EnhancedListView setRequireTouchBeforeDismiss(boolean touchBeforeDismiss) {
-        mTouchBeforeAutoHide = touchBeforeDismiss;
-        return this;
-    }
-
-    /**
-     * Sets the directions in which a list item can be swiped to delete.
-     * By default this is set to {@link SwipeDirection#BOTH} so that an item
-     * can be swiped into both directions.
-     * <p>
-     * <b>Note:</b> This method requires the <i>Swipe to Dismiss</i> feature enabled. Use
-     * {@link #enableSwipeToDismiss()} to enable the feature.
-     *
-     * @param direction The direction to which the swipe should be limited.
-     * @return This {@link de.timroes.android.listview.EnhancedListView}
-     */
-    public EnhancedListView setSwipeDirection(SwipeDirection direction) {
-        mSwipeDirection = direction;
-        return this;
-    }
-
-    /**
-     * Sets the id of the view, that should be moved, when the user swipes an item.
-     * Only the view with the specified id will move, while all other views in the list item, will
-     * stay where they are. This might be usefull to have a background behind the view that is swiped
-     * out, to stay where it is (and maybe explain that the item is going to be deleted).
-     * If you never call this method (or call it with 0), the whole view will be swiped. Also if there
-     * is no view in a list item, with the given id, the whole view will be swiped.
-     * <p>
-     * <b>Note:</b> This method requires the <i>Swipe to Dismiss</i> feature enabled. Use
-     * {@link #enableSwipeToDismiss()} to enable the feature.
-     *
-     * @param swipingLayoutId The id (from R.id) of the view, that should be swiped.
-     * @return This {@link de.timroes.android.listview.EnhancedListView}
-     */
-    public EnhancedListView setSwipingLayout(int swipingLayoutId) {
-        mSwipingLayout = swipingLayoutId;
-        return this;
-    }
-
-    /**
-     * Discard all stored undos and hide the undo popup dialog.
-     * This method must be called in {@link android.app.Activity#onStop()}. Otherwise
-     * {@link EnhancedListView.Undoable#discard()} might not be called for several items, what might
-     * break your data consistency.
-     */
-    public void discardUndo() {
-        for(Undoable undoable : mUndoActions) {
-            undoable.discard();
-        }
-        mUndoActions.clear();
-        if(mUndoPopup.isShowing()) {
-            mUndoPopup.dismiss();
-        }
-    }
-
-    /**
-     * Delete the list item at the specified position. This will animate the item sliding out of the
-     * list and then collapsing until it vanished (same as if the user slides out an item).
-     * <p>
-     * NOTE: If you are using list headers, be aware, that the position argument must take care of
-     * them. Meaning 0 references the first list header. So if you want to delete the first list
-     * item, you have to pass the number of list headers as {@code position}. Most of the times
-     * that shouldn't be a problem, since you most probably will evaluate the position which should
-     * be deleted in a way, that respects the list headers.
-     *
-     * @param position The position of the item in the list.
-     * @throws java.lang.IndexOutOfBoundsException when trying to delete an item outside of the list range.
-     * @throws java.lang.IllegalStateException when this method is called before an {@link EnhancedListView.OnDismissCallback}
-     *      is set via {@link #setDismissCallback(de.timroes.android.listview.EnhancedListView.OnDismissCallback)}.
-     * */
-    public void delete(int position) {
-        if(mDismissCallback == null) {
-            throw new IllegalStateException("You must set an OnDismissCallback, before deleting items.");
-        }
-        if(position < 0 || position >= getCount()) {
-            throw new IndexOutOfBoundsException(String.format("Tried to delete item %d. #items in list: %d", position, getCount()));
-        }
-        View childView = getChildAt(position - getFirstVisiblePosition());
-        View view = null;
-        if(mSwipingLayout > 0) {
-            view = childView.findViewById(mSwipingLayout);
-        }
-        if(view == null) {
-            view = childView;
-        }
-        slideOutView(view, childView, position, true);
-    }
-
-    /**
-     * Slide out a view to the right or left of the list. After the animation has finished, the
-     * view will be dismissed by calling {@link #performDismiss(android.view.View, android.view.View, int)}.
-     *
-     * @param view The view, that should be slided out.
-     * @param childView The whole view of the list item.
-     * @param position The item position of the item.
-     * @param toRightSide Whether it should slide out to the right side.
-     */
-    private void slideOutView(final View view, final View childView, final int position, boolean toRightSide) {
-
-        // Only start new animation, if this view isn't already animated (too fast swiping bug)
-        synchronized(mAnimationLock) {
-            if(mAnimatedViews.contains(view)) {
-                return;
-            }
-            ++mDismissAnimationRefCount;
-            mAnimatedViews.add(view);
-        }
-
-        ViewPropertyAnimator.animate(view)
-                .translationX(toRightSide ? mViewWidth : -mViewWidth)
-                .alpha(0)
-                .setDuration(mAnimationTime)
-                .setListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        performDismiss(view, childView, position);
-                    }
-                });
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-
-        if (!mSwipeEnabled) {
-            return super.onTouchEvent(ev);
-        }
-
-        // Send a delayed message to hide popup
-        if(mTouchBeforeAutoHide && mUndoPopup.isShowing()) {
-            mHideUndoHandler.sendMessageDelayed(mHideUndoHandler.obtainMessage(mValidDelayedMsgId), mUndoHideDelay);
-        }
-
-        // Store width of this list for usage of swipe distance detection
-        if (mViewWidth < 2) {
-            mViewWidth = getWidth();
-        }
-
-        switch (ev.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN: {
-                if (mSwipePaused) {
-                    return super.onTouchEvent(ev);
-                }
-
-                // TODO: ensure this is a finger, and set a flag
-
-                // Find the child view that was touched (perform a hit test)
-                Rect rect = new Rect();
-                int childCount = getChildCount();
-                int[] listViewCoords = new int[2];
-                getLocationOnScreen(listViewCoords);
-                int x = (int) ev.getRawX() - listViewCoords[0];
-                int y = (int) ev.getRawY() - listViewCoords[1];
-                View child;
-                for (int i = getHeaderViewsCount(); i < childCount; i++) {
-                    child = getChildAt(i);
-                    if(child != null) {
-                        child.getHitRect(rect);
-                        if (rect.contains(x, y)) {
-                            // if a specific swiping layout has been giving, use this to swipe.
-                            if(mSwipingLayout > 0) {
-                                View swipingView = child.findViewById(mSwipingLayout);
-                                if(swipingView != null) {
-                                    mSwipeDownView = swipingView;
-                                    mSwipeDownChild = child;
-                                    break;
-                                }
-                            }
-                            // If no swiping layout has been found, swipe the whole child
-                            mSwipeDownView = mSwipeDownChild = child;
-                            break;
-                        }
-                    }
-                }
-
-                if (mSwipeDownView != null) {
-                    // test if the item should be swiped
-                    int position = getPositionForView(mSwipeDownView) - getHeaderViewsCount();
-                    if ((mShouldSwipeCallback == null) ||
-                        mShouldSwipeCallback.onShouldSwipe(this, position)) {
-                    mDownX = ev.getRawX();
-                        mDownPosition = position;
-
-                    mVelocityTracker = VelocityTracker.obtain();
-                    mVelocityTracker.addMovement(ev);
-                    } else {
-                        // set back to null to revert swiping
-                        mSwipeDownView = mSwipeDownChild = null;
-                    }
-                }
-                super.onTouchEvent(ev);
-                return true;
-            }
-
-            case MotionEvent.ACTION_UP: {
-                if (mVelocityTracker == null) {
-                    break;
-                }
-
-                float deltaX = ev.getRawX() - mDownX;
-                mVelocityTracker.addMovement(ev);
-                mVelocityTracker.computeCurrentVelocity(1000);
-                float velocityX = Math.abs(mVelocityTracker.getXVelocity());
-                float velocityY = Math.abs(mVelocityTracker.getYVelocity());
-                boolean dismiss = false;
-                boolean dismissRight = false;
-                if (Math.abs(deltaX) > mViewWidth / 2 && mSwiping) {
-                    dismiss = true;
-                    dismissRight = deltaX > 0;
-                } else if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity
-                        && velocityY < velocityX && mSwiping && isSwipeDirectionValid(mVelocityTracker.getXVelocity())
-                        && deltaX >= mViewWidth * 0.2f) {
-                    dismiss = true;
-                    dismissRight = mVelocityTracker.getXVelocity() > 0;
-                }
-                if (dismiss) {
-                    // dismiss
-                    slideOutView(mSwipeDownView, mSwipeDownChild, mDownPosition, dismissRight);
-                } else if(mSwiping) {
-                    // Swipe back to regular position
-                    ViewPropertyAnimator.animate(mSwipeDownView)
-                            .translationX(0)
-                            .alpha(1)
-                            .setDuration(mAnimationTime)
-                            .setListener(null);
-                }
-                mVelocityTracker = null;
-                mDownX = 0;
-                mSwipeDownView = null;
-                mSwipeDownChild = null;
-                mDownPosition = AbsListView.INVALID_POSITION;
-                mSwiping = false;
-                break;
-            }
-
-            case MotionEvent.ACTION_MOVE: {
-
-                if (mVelocityTracker == null || mSwipePaused) {
-                    break;
-                }
-
-                mVelocityTracker.addMovement(ev);
-                float deltaX = ev.getRawX() - mDownX;
-                // Only start swipe in correct direction
-                if(isSwipeDirectionValid(deltaX)) {
-                    ViewParent parent = getParent();
-                    if(parent != null) {
-                        // If we swipe don't allow parent to intercept touch (e.g. like NavigationDrawer does)
-                        // otherwise swipe would not be working.
-                        parent.requestDisallowInterceptTouchEvent(true);
-                    }
-                    if (Math.abs(deltaX) > mSlop) {
-                        mSwiping = true;
-                        requestDisallowInterceptTouchEvent(true);
-
-                        // Cancel ListView's touch (un-highlighting the item)
-                        MotionEvent cancelEvent = MotionEvent.obtain(ev);
-                        cancelEvent.setAction(MotionEvent.ACTION_CANCEL
-                                | (ev.getActionIndex()
-                                << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
-                        super.onTouchEvent(cancelEvent);
-                    }
-                } else {
-                    // If we swiped into wrong direction, act like this was the new
-                    // touch down point
-                    mDownX = ev.getRawX();
-                    deltaX = 0;
-                }
-
-                if (mSwiping) {
-                    ViewHelper.setTranslationX(mSwipeDownView, deltaX);
-                    ViewHelper.setAlpha(mSwipeDownView, Math.max(0f, Math.min(1f,
-                            1f - 2f * Math.abs(deltaX) / mViewWidth)));
-                    return true;
-                }
-                break;
-            }
-        }
-        return super.onTouchEvent(ev);
-    }
-
-    /**
-     * Animate the dismissed list item to zero-height and fire the dismiss callback when
-     * all dismissed list item animations have completed.
-     *
-     * @param dismissView The view that has been slided out.
-     * @param listItemView The list item view. This is the whole view of the list item, and not just
-     *                     the part, that the user swiped.
-     * @param dismissPosition The position of the view inside the list.
-     */
-    private void performDismiss(final View dismissView, final View listItemView, final int dismissPosition) {
-
-        final ViewGroup.LayoutParams lp = listItemView.getLayoutParams();
-        final int originalLayoutHeight = lp.height;
-
-        int originalHeight = listItemView.getHeight();
-        ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
-
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-
-                // Make sure no other animation is running. Remove animation from running list, that just finished
-                boolean noAnimationLeft;
-                synchronized(mAnimationLock) {
-                    --mDismissAnimationRefCount;
-                    mAnimatedViews.remove(dismissView);
-                    noAnimationLeft = mDismissAnimationRefCount == 0;
-                }
-
-                if (noAnimationLeft) {
-                    // No active animations, process all pending dismisses.
-
-                    for(PendingDismissData dismiss : mPendingDismisses) {
-                        if(mUndoStyle == UndoStyle.SINGLE_POPUP) {
-                            for(Undoable undoable : mUndoActions) {
-                                undoable.discard();
-                            }
-                            mUndoActions.clear();
-                        }
-                        Undoable undoable = mDismissCallback.onDismiss(EnhancedListView.this, dismiss.position);
-                        if(undoable != null) {
-                            mUndoActions.add(undoable);
-                        }
-                        mValidDelayedMsgId++;
-                    }
-
-                    if(!mUndoActions.isEmpty()) {
-                        changePopupText();
-                        changeButtonLabel();
-
-                        // Show undo popup
-                        float yLocationOffset = getResources().getDimension(R.dimen.elv_undo_bottom_offset);
-                        mUndoPopup.setWidth((int)Math.min(mScreenDensity * 400, getWidth() * 0.9f));
-                        mUndoPopup.showAtLocation(EnhancedListView.this,
-                                Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM,
-                                0, (int) yLocationOffset);
-
-                        // Queue the dismiss only if required
-                        if(!mTouchBeforeAutoHide) {
-                            // Send a delayed message to hide popup
-                            mHideUndoHandler.sendMessageDelayed(mHideUndoHandler.obtainMessage(mValidDelayedMsgId),
-                                    mUndoHideDelay);
-                        }
-                    }
-
-                    ViewGroup.LayoutParams lp;
-                    for (PendingDismissData pendingDismiss : mPendingDismisses) {
-                        ViewHelper.setAlpha(pendingDismiss.view, 1f);
-                        ViewHelper.setTranslationX(pendingDismiss.view, 0);
-                        lp = pendingDismiss.childView.getLayoutParams();
-                        lp.height = originalLayoutHeight;
-                        pendingDismiss.childView.setLayoutParams(lp);
-                    }
-
-                    mPendingDismisses.clear();
-                }
-            }
-        });
-
-        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator valueAnimator) {
-                lp.height = (Integer) valueAnimator.getAnimatedValue();
-                listItemView.setLayoutParams(lp);
-            }
-        });
-
-        mPendingDismisses.add(new PendingDismissData(dismissPosition, dismissView, listItemView));
-        animator.start();
-    }
-
-    /**
-     * Changes the text of the undo popup. If more then one item can be undone, the number of deleted
-     * items will be shown. If only one deletion can be undone, the title of this deletion (or a default
-     * string in case the title is {@code null}) will be shown.
-     */
-    private void changePopupText() {
-        String msg = null;
-        if(mUndoActions.size() > 1) {
-            msg = getResources().getString(R.string.elv_n_items_deleted, mUndoActions.size());
-        } else if(mUndoActions.size() >= 1) {
-            // Set title from single undoable or when no multiple deletion string
-            // is given
-            msg = mUndoActions.get(mUndoActions.size() - 1).getTitle();
-
-            if(msg == null) {
-                msg = getResources().getString(R.string.elv_item_deleted);
-            }
-        }
-        mUndoPopupTextView.setText(msg);
-    }
-
-    /**
-     * Changes the label of the undo button.
-     */
-    private void changeButtonLabel() {
-        String msg;
-        if(mUndoActions.size() > 1 && mUndoStyle == UndoStyle.COLLAPSED_POPUP) {
-            msg = getResources().getString(R.string.elv_undo_all);
-        } else {
-            msg = getResources().getString(R.string.elv_undo);
-        }
-        mUndoButton.setText(msg);
-    }
-
-    private OnScrollListener makeScrollListener() {
-        return new OnScrollListener() {
-            @Override
-            public void onScrollStateChanged(AbsListView view, int scrollState) {
-                mSwipePaused = scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
-            }
-
-            @Override
-            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
-            }
-        };
-    }
-
-    /**
-     * Checks whether the delta of a swipe indicates, that the swipe is in the
-     * correct direction, regarding the direction set via
-     * {@link #setSwipeDirection(de.timroes.android.listview.EnhancedListView.SwipeDirection)}
-     *
-     * @param deltaX The delta of x coordinate of the swipe.
-     * @return Whether the delta of a swipe is in the right direction.
-     */
-    private boolean isSwipeDirectionValid(float deltaX) {
-
-        int rtlSign = 1;
-        // On API level 17 and above, check if we are in a Right-To-Left layout
-        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            if(getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
-                rtlSign = -1;
-            }
-        }
-
-        // Check if swipe has been done in the correct direction
-        switch(mSwipeDirection) {
-            default:
-            case BOTH:
-                return true;
-            case START:
-                return rtlSign * deltaX < 0;
-            case END:
-                return rtlSign * deltaX > 0;
-        }
-
-    }
-    
-    @Override
-	protected void onWindowVisibilityChanged(int visibility) {
-		super.onWindowVisibilityChanged(visibility);
-		
-		/*
-		 * If the container window no longer visiable,
-		 * dismiss visible undo popup window so it won't leak,
-		 * cos the container window will be destroyed before dismissing the popup window.
-		 */
-		if(visibility != View.VISIBLE) {
-			discardUndo();
-		}
-	}
-}

libs/EnhancedListView/src/main/res/drawable/elv_undo_btn_bg.xml 🔗

@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-	<item android:state_pressed="true" android:drawable="@drawable/elv_undo_btn_bg_pressed"/> <!-- pressed -->
-	<item android:state_focused="true" android:drawable="@drawable/elv_undo_btn_bg_focused"/> <!-- focused -->
-	<item android:drawable="@color/elv_btn_normal"/> <!-- default -->
-</selector>

libs/EnhancedListView/src/main/res/layout-v19/elv_undo_popup.xml 🔗

@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-			  android:layout_width="fill_parent"
-			  android:layout_height="match_parent"
-			  android:orientation="horizontal"
-			  android:background="@drawable/elv_toast_frame"
-			  android:gravity="center">
-
-	<TextView
-		android:id="@+id/text"
-		android:fontFamily="sans-serif-condensed"
-		android:textSize="16sp"
-		android:layout_weight="1"
-		android:ellipsize="end"
-		android:singleLine="true"
-		android:textColor="@color/elv_popup_text_color"
-		android:layout_width="wrap_content"
-		android:layout_height="wrap_content"
-		android:shadowColor="#BB000000"
-		android:shadowRadius="2.75"/>
-
-	<View
-		android:layout_weight="0"
-		android:layout_marginRight="8dp"
-		android:layout_marginLeft="8dp"
-		android:layout_width="1dp"
-		android:layout_height="match_parent"
-		android:layout_marginTop="5dp"
-		android:layout_marginBottom="5dp"
-		android:background="@color/elv_separator_color"/>
-
-	<Button
-		android:id="@+id/undo"
-		android:fontFamily="sans-serif-condensed"
-		android:textColor="@color/elv_popup_text_color"
-		android:background="@drawable/elv_undo_btn_bg"
-		android:layout_weight="0"
-		android:drawableLeft="@drawable/elv_ic_action_undo"
-		android:layout_width="wrap_content"
-		android:layout_height="match_parent"
-		android:shadowColor="#BB000000"
-		android:shadowRadius="2.75"/>
-</LinearLayout>

libs/EnhancedListView/src/main/res/layout/elv_undo_popup.xml 🔗

@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-		android:layout_width="fill_parent" 
-		android:layout_height="fill_parent" 
-		android:orientation="horizontal"
-		android:background="@drawable/elv_popup_bg"
-		android:paddingRight="8dp"
-		android:gravity="center">
-	<TextView android:id="@+id/text"
-			android:padding="8dp"
-			android:textSize="16sp"
-			android:layout_weight="1"
-			android:singleLine="true"
-			android:ellipsize="end"
-			android:textColor="@color/elv_popup_text_color"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"/>
-	<View
-			android:layout_weight="0"
-			android:layout_marginRight="8dp"
-			android:layout_marginLeft="8dp"
-			android:layout_marginTop="15dp"
-			android:layout_marginBottom="15dp"
-			android:layout_width="1dp"
-			android:layout_height="fill_parent"
-			android:background="@color/elv_separator_color"/>
-	<Button android:id="@+id/undo"
-			android:textColor="@color/elv_popup_text_color"
-			android:background="@drawable/elv_undo_btn_bg"
-			android:drawableLeft="@drawable/elv_ic_action_undo"
-			android:layout_weight="0"
-			android:paddingRight="8dp"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"/>
-</LinearLayout>

libs/EnhancedListView/src/main/res/values/colors.xml 🔗

@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources>
-	<color name="elv_btn_pressed">#ff33b5e5</color>
-	<color name="elv_btn_focused">#ff0099cc</color>
-	<color name="elv_btn_normal">#00000000</color>
-
-	<color name="elv_popup_bg_color">#EE666666</color>
-	<color name="elv_separator_color">#BBBBBB</color>
-	<color name="elv_popup_text_color">#FFFFFF</color>
-</resources>

libs/EnhancedListView/src/main/res/values/dimens.xml 🔗

@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-	<!-- The bottom offset the undo popup should have -->
-	<dimen name="elv_undo_bottom_offset">15dp</dimen>
-	<!-- The touch slop you need to cause a swipe instead of a scroll -->
-	<dimen name="elv_touch_slop">32dp</dimen>
-</resources>

libs/EnhancedListView/src/main/res/values/strings.xml 🔗

@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources>
-	<string name="elv_undo">Undo</string>
-	<string name="elv_undo_all">Undo All</string>
-	<string name="elv_item_deleted">Item deleted</string>
-	<string name="elv_n_items_deleted">%1$s items deleted</string>
-</resources>

libs/EnhancedListView/src/main/res/values/styles.xml 🔗

@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-	<style name="elv_fade_animation">
-		<item name="android:windowEnterAnimation">@anim/elv_popup_show</item>
-		<item name="android:windowExitAnimation">@anim/elv_popup_hide</item>
-	</style>
-</resources>

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -131,7 +131,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
 	public static final String STATE_PHOTO_URI = ConversationFragment.class.getName() + ".take_photo_uri";
 	private static final String STATE_LAST_MESSAGE_UUID = "state_last_message_uuid";
 
-	final protected List<Message> messageList = new ArrayList<>();
+	private final List<Message> messageList = new ArrayList<>();
 	private final PendingItem<ActivityResult> postponedActivityResult = new PendingItem<>();
 	private final PendingItem<String> pendingConversationsUuid = new PendingItem<>();
 	private final PendingItem<Bundle> pendingExtras = new PendingItem<>();
@@ -214,31 +214,33 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
 								return;
 							}
 							runOnUiThread(() -> {
-								final int oldPosition = binding.messagesView.getFirstVisiblePosition();
-								Message message = null;
-								int childPos;
-								for (childPos = 0; childPos + oldPosition < messageList.size(); ++childPos) {
-									message = messageList.get(oldPosition + childPos);
-									if (message.getType() != Message.TYPE_STATUS) {
-										break;
+								synchronized (messageList) {
+									final int oldPosition = binding.messagesView.getFirstVisiblePosition();
+									Message message = null;
+									int childPos;
+									for (childPos = 0; childPos + oldPosition < messageList.size(); ++childPos) {
+										message = messageList.get(oldPosition + childPos);
+										if (message.getType() != Message.TYPE_STATUS) {
+											break;
+										}
 									}
+									final String uuid = message != null ? message.getUuid() : null;
+									View v = binding.messagesView.getChildAt(childPos);
+									final int pxOffset = (v == null) ? 0 : v.getTop();
+									ConversationFragment.this.conversation.populateWithMessages(ConversationFragment.this.messageList);
+									try {
+										updateStatusMessages();
+									} catch (IllegalStateException e) {
+										Log.d(Config.LOGTAG, "caught illegal state exception while updating status messages");
+									}
+									messageListAdapter.notifyDataSetChanged();
+									int pos = Math.max(getIndexOf(uuid, messageList), 0);
+									binding.messagesView.setSelectionFromTop(pos, pxOffset);
+									if (messageLoaderToast != null) {
+										messageLoaderToast.cancel();
+									}
+									conversation.messagesLoaded.set(true);
 								}
-								final String uuid = message != null ? message.getUuid() : null;
-								View v = binding.messagesView.getChildAt(childPos);
-								final int pxOffset = (v == null) ? 0 : v.getTop();
-								ConversationFragment.this.conversation.populateWithMessages(ConversationFragment.this.messageList);
-								try {
-									updateStatusMessages();
-								} catch (IllegalStateException e) {
-									Log.d(Config.LOGTAG, "caught illegal state exception while updating status messages");
-								}
-								messageListAdapter.notifyDataSetChanged();
-								int pos = Math.max(getIndexOf(uuid, messageList), 0);
-								binding.messagesView.setSelectionFromTop(pos, pxOffset);
-								if (messageLoaderToast != null) {
-									messageLoaderToast.cancel();
-								}
-								conversation.messagesLoaded.set(true);
 							});
 						}
 
@@ -1586,7 +1588,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
 			if (pos >= 0) {
 				Message message = null;
 				for (int i = pos; i >= 0; --i) {
-					message = (Message) binding.messagesView.getItemAtPosition(i);
+					try {
+						message = (Message) binding.messagesView.getItemAtPosition(i);
+					} catch (ArrayIndexOutOfBoundsException e) {
+						//should not happen if we synchronize properly. however if that fails we just gonna try item -1
+						continue;
+					}
 					if (message.getType() != Message.TYPE_STATUS) {
 						break;
 					}

src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java 🔗

@@ -32,7 +32,13 @@ package eu.siacs.conversations.ui;
 import android.app.Activity;
 import android.app.Fragment;
 import android.databinding.DataBindingUtil;
+import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.os.Bundle;
+import android.support.design.widget.Snackbar;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -41,7 +47,6 @@ import android.view.ViewGroup;
 import java.util.ArrayList;
 import java.util.List;
 
-import de.timroes.android.listview.EnhancedListView;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.databinding.FragmentConversationsOverviewBinding;
@@ -49,10 +54,15 @@ import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.ui.adapter.ConversationAdapter;
 import eu.siacs.conversations.ui.interfaces.OnConversationArchived;
 import eu.siacs.conversations.ui.interfaces.OnConversationSelected;
+import eu.siacs.conversations.ui.util.Color;
+import eu.siacs.conversations.ui.util.PendingActionHelper;
 import eu.siacs.conversations.ui.util.PendingItem;
 import eu.siacs.conversations.ui.util.ScrollState;
 
-public class ConversationsOverviewFragment extends XmppFragment implements EnhancedListView.OnDismissCallback {
+import static android.support.v7.widget.helper.ItemTouchHelper.LEFT;
+import static android.support.v7.widget.helper.ItemTouchHelper.RIGHT;
+
+public class ConversationsOverviewFragment extends XmppFragment {
 
 	private static final String STATE_SCROLL_POSITION = ConversationsOverviewFragment.class.getName()+".scroll_state";
 
@@ -62,6 +72,98 @@ public class ConversationsOverviewFragment extends XmppFragment implements Enhan
 	private FragmentConversationsOverviewBinding binding;
 	private ConversationAdapter conversationsAdapter;
 	private XmppActivity activity;
+	private PendingActionHelper pendingActionHelper = new PendingActionHelper();
+
+	private ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0,LEFT|RIGHT) {
+		@Override
+		public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
+			//todo maybe we can manually changing the position of the conversation
+			return false;
+		}
+
+		@Override
+		public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
+									float dX, float dY, int actionState, boolean isCurrentlyActive) {
+			super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
+			if(actionState != ItemTouchHelper.ACTION_STATE_IDLE){
+				Paint paint = new Paint();
+				paint.setColor(Color.get(activity,R.attr.conversations_overview_background));
+				paint.setStyle(Paint.Style.FILL);
+				c.drawRect(viewHolder.itemView.getLeft(),viewHolder.itemView.getTop()
+						,viewHolder.itemView.getRight(),viewHolder.itemView.getBottom(), paint);
+			}
+		}
+
+		@Override
+		public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+			super.clearView(recyclerView, viewHolder);
+			viewHolder.itemView.setAlpha(1f);
+		}
+
+		@Override
+		public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
+			pendingActionHelper.execute();
+			int position = viewHolder.getLayoutPosition();
+			try {
+				swipedConversation.push(conversations.get(position));
+			} catch (IndexOutOfBoundsException e) {
+				return;
+			}
+			conversationsAdapter.remove(swipedConversation.peek(), position);
+			activity.xmppConnectionService.markRead(swipedConversation.peek());
+
+			if (position == 0 && conversationsAdapter.getItemCount() == 0) {
+				final Conversation c = swipedConversation.pop();
+				activity.xmppConnectionService.archiveConversation(c);
+				return;
+			}
+			final boolean formerlySelected = ConversationFragment.getConversation(getActivity()) == swipedConversation.peek();
+			if (activity instanceof OnConversationArchived) {
+				((OnConversationArchived) activity).onConversationArchived(swipedConversation.peek());
+			}
+			boolean isMuc = swipedConversation.peek().getMode() == Conversation.MODE_MULTI;
+			int title = isMuc ? R.string.title_undo_swipe_out_muc : R.string.title_undo_swipe_out_conversation;
+
+			pendingActionHelper.push(() -> {
+				Conversation c = swipedConversation.pop();
+				if(c != null){
+					if (!c.isRead() && c.getMode() == Conversation.MODE_SINGLE) {
+						return;
+					}
+					activity.xmppConnectionService.archiveConversation(c);
+				}
+			});
+			Snackbar.make(binding.list, title, 5000)
+					.setAction(R.string.undo, v -> {
+						pendingActionHelper.undo();
+						Conversation c = swipedConversation.pop();
+						conversationsAdapter.insert(c, position);
+						if (formerlySelected) {
+							if (activity instanceof OnConversationSelected) {
+								((OnConversationSelected) activity).onConversationSelected(c);
+							}
+						}
+						LinearLayoutManager layoutManager = (LinearLayoutManager) binding.list.getLayoutManager();
+						if (position > layoutManager.findLastVisibleItemPosition()) {
+							binding.list.smoothScrollToPosition(position);
+						}
+					})
+					.addCallback(new Snackbar.Callback() {
+						@Override
+						public void onDismissed(Snackbar transientBottomBar, int event) {
+							switch (event) {
+								case DISMISS_EVENT_SWIPE:
+								case DISMISS_EVENT_TIMEOUT:
+									pendingActionHelper.execute();
+									break;
+							}
+						}
+					})
+					.show();
+		}
+	};
+
+	private ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
 
 	public static Conversation getSuggestion(Activity activity) {
 		final Conversation exception;
@@ -112,6 +214,13 @@ public class ConversationsOverviewFragment extends XmppFragment implements Enhan
 		}
 	}
 
+	@Override
+	public void onPause() {
+		Log.d(Config.LOGTAG,"ConversationsOverviewFragment.onPause()");
+		pendingActionHelper.execute();
+		super.onPause();
+	}
+
 	@Override
 	public void onDetach() {
 		super.onDetach();
@@ -125,22 +234,16 @@ public class ConversationsOverviewFragment extends XmppFragment implements Enhan
 		this.binding.fab.setOnClickListener((view) -> StartConversationActivity.launch(getActivity()));
 
 		this.conversationsAdapter = new ConversationAdapter(this.activity, this.conversations);
-		this.binding.list.setAdapter(this.conversationsAdapter);
-		this.binding.list.setOnItemClickListener((parent, view, position, id) -> {
-			Conversation conversation = this.conversations.get(position);
+		this.conversationsAdapter.setConversationClickListener((view, conversation) -> {
 			if (activity instanceof OnConversationSelected) {
 				((OnConversationSelected) activity).onConversationSelected(conversation);
 			} else {
 				Log.w(ConversationsOverviewFragment.class.getCanonicalName(), "Activity does not implement OnConversationSelected");
 			}
 		});
-		this.binding.list.setDismissCallback(this);
-		this.binding.list.enableSwipeToDismiss();
-		this.binding.list.setSwipeDirection(EnhancedListView.SwipeDirection.BOTH);
-		this.binding.list.setSwipingLayout(R.id.swipeable_item);
-		this.binding.list.setUndoStyle(EnhancedListView.UndoStyle.SINGLE_POPUP);
-		this.binding.list.setUndoHideDelay(5000);
-		this.binding.list.setRequireTouchBeforeDismiss(false);
+		this.binding.list.setAdapter(this.conversationsAdapter);
+		this.binding.list.setLayoutManager(new LinearLayoutManager(getActivity(),LinearLayoutManager.VERTICAL,false));
+		this.touchHelper.attachToRecyclerView(this.binding.list);
 		return binding.getRoot();
 	}
 
@@ -162,7 +265,8 @@ public class ConversationsOverviewFragment extends XmppFragment implements Enhan
 		if (this.binding == null) {
 			return null;
 		}
-		int position = this.binding.list.getFirstVisiblePosition();
+		LinearLayoutManager layoutManager = (LinearLayoutManager) this.binding.list.getLayoutManager();
+		int position = layoutManager.findFirstVisibleItemPosition();
 		final View view = this.binding.list.getChildAt(0);
 		if (view != null) {
 			return new ScrollState(position,view.getTop());
@@ -198,7 +302,7 @@ public class ConversationsOverviewFragment extends XmppFragment implements Enhan
 			if (removed.isRead()) {
 				this.conversations.remove(removed);
 			} else {
-				this.binding.list.discardUndo(); //will be ignored during discard when conversation is unRead
+				pendingActionHelper.execute();
 			}
 		}
 		this.conversationsAdapter.notifyDataSetChanged();
@@ -210,62 +314,8 @@ public class ConversationsOverviewFragment extends XmppFragment implements Enhan
 
 	private void setScrollPosition(ScrollState scrollPosition) {
 		if (scrollPosition != null) {
-			this.binding.list.setSelectionFromTop(scrollPosition.position, scrollPosition.offset);
+			LinearLayoutManager layoutManager = (LinearLayoutManager) binding.list.getLayoutManager();
+			layoutManager.scrollToPositionWithOffset(scrollPosition.position, scrollPosition.offset);
 		}
 	}
-
-	@Override
-	public EnhancedListView.Undoable onDismiss(EnhancedListView listView, int position) {
-		try {
-			swipedConversation.push(this.conversationsAdapter.getItem(position));
-		} catch (IndexOutOfBoundsException e) {
-			return null;
-		}
-		this.conversationsAdapter.remove(swipedConversation.peek());
-		this.activity.xmppConnectionService.markRead(swipedConversation.peek());
-
-		if (position == 0 && this.conversationsAdapter.getCount() == 0) {
-			final Conversation c = swipedConversation.pop();
-			activity.xmppConnectionService.archiveConversation(c);
-			return null;
-		}
-		final boolean formerlySelected = ConversationFragment.getConversation(getActivity()) == swipedConversation.peek();
-		if (activity instanceof OnConversationArchived) {
-			((OnConversationArchived) activity).onConversationArchived(swipedConversation.peek());
-		}
-		return new EnhancedListView.Undoable() {
-
-			@Override
-			public void undo() {
-				Conversation c = swipedConversation.pop();
-				conversationsAdapter.insert(c, position);
-				if (formerlySelected) {
-					if (activity instanceof OnConversationSelected) {
-						((OnConversationSelected) activity).onConversationSelected(c);
-					}
-				}
-				if (position > listView.getLastVisiblePosition()) {
-					listView.smoothScrollToPosition(position);
-				}
-			}
-
-			@Override
-			public void discard() {
-				Conversation c = swipedConversation.pop();
-				if (!c.isRead() && c.getMode() == Conversation.MODE_SINGLE) {
-					return;
-				}
-				activity.xmppConnectionService.archiveConversation(c);
-			}
-
-			@Override
-			public String getTitle() {
-				if (swipedConversation.peek().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);
-				}
-			}
-		};
-	}
 }

src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java 🔗

@@ -5,13 +5,11 @@ import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Bundle;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ListView;
 import android.widget.Toast;
 
 import java.net.URLConnection;
@@ -58,7 +56,7 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
 	private Share share;
 
 	private static final int REQUEST_START_NEW_CONVERSATION = 0x0501;
-	private ListView mListView;
+	private RecyclerView mListView;
 	private ConversationAdapter mAdapter;
 	private List<Conversation> mConversations = new ArrayList<>();
 	private Toast mToast;
@@ -170,15 +168,9 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
 
 		mListView = findViewById(R.id.choose_conversation_list);
 		mAdapter = new ConversationAdapter(this, this.mConversations);
+		mListView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));
 		mListView.setAdapter(mAdapter);
-		mListView.setOnItemClickListener(new OnItemClickListener() {
-
-			@Override
-			public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3) {
-				share(mConversations.get(position));
-			}
-		});
-
+		mAdapter.setConversationClickListener((view, conversation) -> share(conversation));
 		this.share = new Share();
 	}
 

src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java 🔗

@@ -8,12 +8,11 @@ import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.support.annotation.NonNull;
-import android.util.Log;
+import android.support.v7.widget.RecyclerView;
 import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
 import android.widget.ImageView;
 import android.widget.TextView;
 
@@ -21,28 +20,26 @@ import java.lang.ref.WeakReference;
 import java.util.List;
 import java.util.concurrent.RejectedExecutionException;
 
-import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.Transferable;
-import eu.siacs.conversations.ui.ConversationFragment;
 import eu.siacs.conversations.ui.XmppActivity;
-import eu.siacs.conversations.ui.util.Color;
 import eu.siacs.conversations.ui.widget.UnreadCountCustomView;
 import eu.siacs.conversations.utils.EmojiWrapper;
 import eu.siacs.conversations.utils.IrregularUnicodeDetector;
 import eu.siacs.conversations.utils.UIHelper;
 import rocks.xmpp.addr.Jid;
 
-public class ConversationAdapter extends ArrayAdapter<Conversation> {
+public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapter.ConversationViewHolder> {
 
 	private XmppActivity activity;
-	private Conversation selectedConversation = null;
+	private List<Conversation> conversations;
+	private OnConversationClickListener listener;
 
 	public ConversationAdapter(XmppActivity activity, List<Conversation> conversations) {
-		super(activity, 0, conversations);
 		this.activity = activity;
+		this.conversations = conversations;
 	}
 
 	private static boolean cancelPotentialWork(Conversation conversation, ImageView imageView) {
@@ -70,20 +67,21 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
 		return null;
 	}
 
+	@NonNull
 	@Override
-	public @NonNull
-	View getView(int position, View view, @NonNull ViewGroup parent) {
-		if (view == null) {
-			LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-			view = inflater.inflate(R.layout.conversation_list_row, parent, false);
-		}
-		ViewHolder viewHolder = ViewHolder.get(view);
-		Conversation conversation = getItem(position);
+	public ConversationViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+		LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+		View view = inflater.inflate(R.layout.conversation_list_row, parent, false);
+		ConversationViewHolder conversationViewHolder = ConversationViewHolder.get(view);
+		return conversationViewHolder;
+	}
+
+	@Override
+	public void onBindViewHolder(@NonNull ConversationViewHolder viewHolder, int position) {
+		Conversation conversation = conversations.get(position);
 		if (conversation == null) {
-			return view;
+			return;
 		}
-		int c = Color.get(activity, conversation == selectedConversation ? R.attr.color_background_secondary : R.attr.color_background_primary);
-		viewHolder.swipeableItem.setBackgroundColor(c);
 		if (conversation.getMode() == Conversation.MODE_SINGLE || activity.useSubjectToIdentifyConference()) {
 			CharSequence name = conversation.getName();
 			if (name instanceof Jid) {
@@ -218,14 +216,16 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
 		}
 		viewHolder.timestamp.setText(UIHelper.readableTimeDifference(activity, timestamp));
 		loadAvatar(conversation, viewHolder.avatar);
-
-		return view;
+		viewHolder.itemView.setOnClickListener(v -> listener.onConversationClick(v,conversation));
 	}
 
 	@Override
-	public void notifyDataSetChanged() {
-		this.selectedConversation = ConversationFragment.getConversation(activity);
-		super.notifyDataSetChanged();
+	public int getItemCount() {
+		return conversations.size();
+	}
+
+	public void setConversationClickListener(OnConversationClickListener listener) {
+		this.listener = listener;
 	}
 
 	private void loadAvatar(Conversation conversation, ImageView imageView) {
@@ -249,8 +249,17 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
 		}
 	}
 
-	public static class ViewHolder {
-		private View swipeableItem;
+	public void insert(Conversation c, int position) {
+		conversations.add(position,c);
+		notifyDataSetChanged();
+	}
+
+	public void remove(Conversation conversation,int position) {
+		conversations.remove(conversation);
+		notifyItemRemoved(position);
+	}
+
+	public static class ConversationViewHolder extends RecyclerView.ViewHolder {
 		private TextView name;
 		private TextView lastMessage;
 		private ImageView lastMessageIcon;
@@ -260,26 +269,25 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
 		private UnreadCountCustomView unreadCount;
 		private ImageView avatar;
 
-		private ViewHolder() {
-
+		private ConversationViewHolder(View view) {
+			super(view);
 		}
 
-		public static ViewHolder get(View layout) {
-			ViewHolder viewHolder = (ViewHolder) layout.getTag();
-			if (viewHolder == null) {
-				viewHolder = new ViewHolder();
-				viewHolder.swipeableItem = layout.findViewById(R.id.swipeable_item);
-				viewHolder.name = layout.findViewById(R.id.conversation_name);
-				viewHolder.lastMessage = layout.findViewById(R.id.conversation_lastmsg);
-				viewHolder.lastMessageIcon = layout.findViewById(R.id.conversation_lastmsg_img);
-				viewHolder.timestamp = layout.findViewById(R.id.conversation_lastupdate);
-				viewHolder.sender = layout.findViewById(R.id.sender_name);
-				viewHolder.notificationIcon = layout.findViewById(R.id.notification_status);
-				viewHolder.unreadCount = layout.findViewById(R.id.unread_count);
-				viewHolder.avatar = layout.findViewById(R.id.conversation_image);
-				layout.setTag(viewHolder);
+		public static ConversationViewHolder get(View layout) {
+			ConversationViewHolder conversationViewHolder = (ConversationViewHolder) layout.getTag();
+			if (conversationViewHolder == null) {
+				conversationViewHolder = new ConversationViewHolder(layout);
+				conversationViewHolder.name = layout.findViewById(R.id.conversation_name);
+				conversationViewHolder.lastMessage = layout.findViewById(R.id.conversation_lastmsg);
+				conversationViewHolder.lastMessageIcon = layout.findViewById(R.id.conversation_lastmsg_img);
+				conversationViewHolder.timestamp = layout.findViewById(R.id.conversation_lastupdate);
+				conversationViewHolder.sender = layout.findViewById(R.id.sender_name);
+				conversationViewHolder.notificationIcon = layout.findViewById(R.id.notification_status);
+				conversationViewHolder.unreadCount = layout.findViewById(R.id.unread_count);
+				conversationViewHolder.avatar = layout.findViewById(R.id.conversation_image);
+				layout.setTag(conversationViewHolder);
 			}
-			return viewHolder;
+			return conversationViewHolder;
 		}
 	}
 
@@ -321,4 +329,8 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
 			}
 		}
 	}
+
+	public interface OnConversationClickListener {
+		void onConversationClick(View view, Conversation conversation);
+	}
 }

src/main/java/eu/siacs/conversations/ui/util/PendingActionHelper.java 🔗

@@ -0,0 +1,29 @@
+package eu.siacs.conversations.ui.util;
+
+/**
+ * Created by mxf on 2018/4/3.
+ */
+
+public class PendingActionHelper {
+
+    private PendingAction pendingAction;
+
+    public void push(PendingAction pendingAction) {
+        this.pendingAction = pendingAction;
+    }
+
+    public void execute() {
+        if(pendingAction != null){
+            pendingAction.execute();
+            pendingAction = null;
+        }
+    }
+
+    public void undo() {
+        pendingAction = null;
+    }
+
+    public interface PendingAction {
+        void execute();
+    }
+}

src/main/res/layout/activity_share_with.xml 🔗

@@ -8,7 +8,7 @@
 
     <include layout="@layout/toolbar" />
 
-    <ListView
+    <android.support.v7.widget.RecyclerView
         android:id="@+id/choose_conversation_list"
         android:layout_width="match_parent"
         android:layout_height="match_parent"

src/main/res/layout/conversation_list_row.xml 🔗

@@ -4,13 +4,7 @@
              android:layout_height="wrap_content"
              android:descendantFocusability="blocksDescendants">
 
-    <View
-        android:layout_width="fill_parent"
-        android:layout_height="match_parent"
-        android:background="?attr/conversations_overview_background"/>
-
     <FrameLayout
-        android:id="@+id/swipeable_item"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:background="?attr/color_background_primary">
@@ -81,7 +75,7 @@
                             android:layout_width="?attr/IconSize"
                             android:layout_height="?attr/IconSize"
                             android:layout_marginRight="?attr/TextSeparation"/>
-                      
+
                         <TextView
                             android:id="@+id/conversation_lastmsg"
                             android:layout_width="match_parent"

src/main/res/layout/fragment_conversations_overview.xml 🔗

@@ -1,18 +1,17 @@
 <layout xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <FrameLayout
+    <android.support.design.widget.CoordinatorLayout
         android:background="?attr/color_background_primary"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
 
 
-        <de.timroes.android.listview.EnhancedListView
+        <android.support.v7.widget.RecyclerView
             android:id="@+id/list"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
             android:background="?attr/color_background_primary"
-            android:divider="@android:color/transparent"
-            android:dividerHeight="0dp"/>
+           />
 
         <android.support.design.widget.FloatingActionButton
             android:id="@+id/fab"
@@ -21,5 +20,5 @@
             android:layout_gravity="end|bottom"
             android:layout_margin="16dp"
             android:src="@drawable/ic_chat_white_24dp"/>
-    </FrameLayout>
+    </android.support.design.widget.CoordinatorLayout>
 </layout>

src/main/res/values/strings.xml 🔗

@@ -749,4 +749,5 @@
     <string name="medium">Medium</string>
     <string name="large">Large</string>
     <string name="not_encrypted_for_this_device">Message was not encrypted for this device.</string>
+    <string name="undo">undo</string>
 </resources>