From de23b37ee2a79d67e02fc63da3d0133e1ba79f2e Mon Sep 17 00:00:00 2001 From: daivat15 Date: Thu, 11 Jan 2018 13:39:55 +0530 Subject: [PATCH] Gradle Update to 3.0.1 with aapt Gradle Update to 3.0.1 with aapt Gradle Update to 3.0.1 with aapt fix try --- build.gradle | 2 +- gradle.properties | 3 +- libs/EnhancedListView/build.gradle | 98 ++ .../src/main/AndroidManifest.xml | 6 + .../android/listview/EnhancedListView.java | 969 ++++++++++++++++++ .../src/main/res/anim/elv_popup_hide.xml | 7 + .../src/main/res/anim/elv_popup_show.xml | 7 + .../res/drawable-hdpi/elv_ic_action_undo.png | Bin 0 -> 813 bytes .../res/drawable-hdpi/elv_toast_frame.9.png | Bin 0 -> 1249 bytes .../res/drawable-ldpi/elv_ic_action_undo.png | Bin 0 -> 3104 bytes .../res/drawable-ldpi/elv_toast_frame.9.png | Bin 0 -> 420 bytes .../res/drawable-mdpi/elv_ic_action_undo.png | Bin 0 -> 545 bytes .../res/drawable-mdpi/elv_toast_frame.9.png | Bin 0 -> 562 bytes .../res/drawable-xhdpi/elv_ic_action_undo.png | Bin 0 -> 1068 bytes .../res/drawable-xhdpi/elv_toast_frame.9.png | Bin 0 -> 1462 bytes .../drawable-xxhdpi/elv_ic_action_undo.png | Bin 0 -> 1687 bytes .../res/drawable-xxhdpi/elv_toast_frame.9.png | Bin 0 -> 1979 bytes .../src/main/res/drawable/elv_popup_bg.xml | 5 + .../src/main/res/drawable/elv_undo_btn_bg.xml | 6 + .../res/drawable/elv_undo_btn_bg_focused.xml | 5 + .../res/drawable/elv_undo_btn_bg_pressed.xml | 5 + .../main/res/layout-v19/elv_undo_popup.xml | 43 + .../src/main/res/layout/elv_undo_popup.xml | 35 + .../src/main/res/values-v19/colors.xml | 4 + .../src/main/res/values/colors.xml | 10 + .../src/main/res/values/dimens.xml | 7 + .../src/main/res/values/strings.xml | 7 + .../src/main/res/values/styles.xml | 7 + settings.gradle | 2 +- src/main/res/values-v21/themes.xml | 212 ++-- src/main/res/values/themes.xml | 212 ++-- 31 files changed, 1436 insertions(+), 216 deletions(-) create mode 100644 libs/EnhancedListView/build.gradle create mode 100644 libs/EnhancedListView/src/main/AndroidManifest.xml create mode 100644 libs/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java create mode 100644 libs/EnhancedListView/src/main/res/anim/elv_popup_hide.xml create mode 100644 libs/EnhancedListView/src/main/res/anim/elv_popup_show.xml create mode 100644 libs/EnhancedListView/src/main/res/drawable-hdpi/elv_ic_action_undo.png create mode 100644 libs/EnhancedListView/src/main/res/drawable-hdpi/elv_toast_frame.9.png create mode 100644 libs/EnhancedListView/src/main/res/drawable-ldpi/elv_ic_action_undo.png create mode 100644 libs/EnhancedListView/src/main/res/drawable-ldpi/elv_toast_frame.9.png create mode 100644 libs/EnhancedListView/src/main/res/drawable-mdpi/elv_ic_action_undo.png create mode 100644 libs/EnhancedListView/src/main/res/drawable-mdpi/elv_toast_frame.9.png create mode 100644 libs/EnhancedListView/src/main/res/drawable-xhdpi/elv_ic_action_undo.png create mode 100644 libs/EnhancedListView/src/main/res/drawable-xhdpi/elv_toast_frame.9.png create mode 100644 libs/EnhancedListView/src/main/res/drawable-xxhdpi/elv_ic_action_undo.png create mode 100644 libs/EnhancedListView/src/main/res/drawable-xxhdpi/elv_toast_frame.9.png create mode 100644 libs/EnhancedListView/src/main/res/drawable/elv_popup_bg.xml create mode 100644 libs/EnhancedListView/src/main/res/drawable/elv_undo_btn_bg.xml create mode 100644 libs/EnhancedListView/src/main/res/drawable/elv_undo_btn_bg_focused.xml create mode 100644 libs/EnhancedListView/src/main/res/drawable/elv_undo_btn_bg_pressed.xml create mode 100644 libs/EnhancedListView/src/main/res/layout-v19/elv_undo_popup.xml create mode 100644 libs/EnhancedListView/src/main/res/layout/elv_undo_popup.xml create mode 100644 libs/EnhancedListView/src/main/res/values-v19/colors.xml create mode 100644 libs/EnhancedListView/src/main/res/values/colors.xml create mode 100644 libs/EnhancedListView/src/main/res/values/dimens.xml create mode 100644 libs/EnhancedListView/src/main/res/values/strings.xml create mode 100644 libs/EnhancedListView/src/main/res/values/styles.xml diff --git a/build.gradle b/build.gradle index 0d05d74c3..b8cc47e38 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,7 @@ ext { dependencies { implementation project(':libs:MemorizingTrustManager') + implementation project(':libs:EnhancedListView') playstoreImplementation 'com.google.android.gms:play-services-gcm:11.8.0' implementation 'org.sufficientlysecure:openpgp-api:10.0' implementation 'com.soundcloud.android:android-crop:1.0.1@aar' @@ -47,7 +48,6 @@ dependencies { implementation 'com.google.zxing:core:3.2.1' implementation 'com.google.zxing:android-integration:3.2.1' implementation 'de.measite.minidns:minidns-hla:0.2.4' - implementation 'de.timroes.android:EnhancedListView:0.3.4' implementation 'me.leolin:ShortcutBadger:1.1.19@aar' implementation 'com.kyleduo.switchbutton:library:1.2.8' implementation 'org.whispersystems:signal-protocol-java:2.6.2' diff --git a/gradle.properties b/gradle.properties index b8a807e22..4a9594aee 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1 @@ -org.gradle.jvmargs=-Xmx2048M -android.enableAapt2=false +org.gradle.jvmargs=-Xmx2048M \ No newline at end of file diff --git a/libs/EnhancedListView/build.gradle b/libs/EnhancedListView/build.gradle new file mode 100644 index 000000000..3e5a9e176 --- /dev/null +++ b/libs/EnhancedListView/build.gradle @@ -0,0 +1,98 @@ +apply plugin: 'com.android.library' + +repositories { + mavenCentral() + google() +} + +dependencies { + compile 'com.android.support:support-v4:27.0.2' + compile 'com.nineoldandroids:library:2.4.0' +} + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.2" + + 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" + +if(project.hasProperty("EnhancedListView.properties") && new File(project.property("EnhancedListView.properties")).exists()) { + + Properties props = new Properties() + props.load(new FileInputStream(file(project.property("EnhancedListView.properties")))) + + gradle.taskGraph.whenReady { taskGraph -> + if (taskGraph.allTasks.any { it instanceof Sign }) { + allprojects { ext."signing.keyId" = props['signing.keyId'] } + allprojects { ext."signing.secretKeyRingFile" = props['signing.secretKeyRingFile'] } + allprojects { ext."signing.password" = props['signing.password'] } + } + } + + signing { + required { has("release") && gradle.taskGraph.hasTask("uploadArchives") } + sign configurations.archives + } + + uploadArchives { + + configuration = configurations.archives + repositories.mavenDeployer { + + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + + repository(url: props['sonatypeRepo']) { + authentication(userName: props['sonatypeUsername'], password: props['sonatypePassword']) + } + + pom.project { + + name 'EnhancedListView' + packaging 'aar' + description 'ListView with enhanced features for Android' + url 'https://github.com/timroes/EnhancedListView' + + scm { + url 'scm:git@github.com:timroes/EnhancedListView.git' + connection 'scm:git@github.com:timroes/EnhancedListView.git' + developerConnection 'scm:git@github.com:timroes/EnhancedListView.git' + } + + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } + } + + developers { + developer { + id 'timroes' + name 'Tim Roes' + email 'mail@timroes.de' + } + } + + } + + } + } + +} diff --git a/libs/EnhancedListView/src/main/AndroidManifest.xml b/libs/EnhancedListView/src/main/AndroidManifest.xml new file mode 100644 index 000000000..5f9f17429 --- /dev/null +++ b/libs/EnhancedListView/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/libs/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java b/libs/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java new file mode 100644 index 000000000..45222f968 --- /dev/null +++ b/libs/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java @@ -0,0 +1,969 @@ +/* + * 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 + */ +public class EnhancedListView extends ListView { + + /** + * Defines the style in which undos 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 Undo all 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. + *

+ * Note: This method requires the Swipe to Dismiss 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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 { + + 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 mUndoActions = new ArrayList(); + private SortedSet mPendingDismisses = new TreeSet(); + private List mAnimatedViews = new LinkedList(); + 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 Swipe to Dismiss 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 + * undo 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 undo 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 Swipe to Dismiss 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 Swipe to Dismiss 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. + *

+ * Note: This method requires the Swipe to Dismiss 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. + *

+ * Note: This method requires the Swipe to Dismiss 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). + *

+ * 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(); + } + } +} diff --git a/libs/EnhancedListView/src/main/res/anim/elv_popup_hide.xml b/libs/EnhancedListView/src/main/res/anim/elv_popup_hide.xml new file mode 100644 index 000000000..9fd948e92 --- /dev/null +++ b/libs/EnhancedListView/src/main/res/anim/elv_popup_hide.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/libs/EnhancedListView/src/main/res/anim/elv_popup_show.xml b/libs/EnhancedListView/src/main/res/anim/elv_popup_show.xml new file mode 100644 index 000000000..9749c97d9 --- /dev/null +++ b/libs/EnhancedListView/src/main/res/anim/elv_popup_show.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/libs/EnhancedListView/src/main/res/drawable-hdpi/elv_ic_action_undo.png b/libs/EnhancedListView/src/main/res/drawable-hdpi/elv_ic_action_undo.png new file mode 100644 index 0000000000000000000000000000000000000000..67c2496f54bfee190d62466b40a9d85a7b51665f GIT binary patch literal 813 zcmV+|1JeA7P);L<{wbx$zG}KZ4PBqs8bD z0Ox|;cm(hQ!2E2++Jhz}V^#wI;{!3g7Oh7^jD)p50OrSHxC6~ksq`n#eN8$4mvg_+ zck~&(MX%5^^q6V)-ecDM*TrcHVSXXKcA+t$0~-CcYCT6cP!}+-ePZVVV17A)JCUYO zS?wo*FQHRKtKtGMn82ISYBZoMf+X%6KDPn!$aS1tfSy29Vjjo32gbPu_q?uK2?OGu zOMhno1p`9n1tA!-o#xBsUIrY*`C({e0zaA0X-G@4o+W8b_d`1nS{yQ4A%Fxz(`X&q zfMias@G_b_2v^WoOR`otyPXB#aCCs4fEbv`@jf)s2f&CkisNHwx@Gno-t;lf8>=xqQEDgZO;%El?kXP=k-+|D*wbHugBymra4iIA8 zNP^4@!faOo&;zvB7Bq!o7&id4vK_Tz-UdL=ph^JD#pQ9!=^X$zRRv%fM)z7~uLH27 zDgawC+HRRW1Hh4%2S^@j=P^3oGP@IiYb_0cc|FbdqeYg{KYU-Bx1H$MRS5I5Fu4)U zNy&2V{8yho1HkN1#?xkS3YviyAZ#_ysF=$6mllYKRX zWb_MqhxE4f0zEnCaTB%PhR!me76%j2|Xwd>e5rI+) zg-{S$R#p^gZsa!q#`HUSjAuIfmKo>GymR5l41>OR@9*Ao&vNfM85!noQ3b5o^7He@ z0<%0G&nmCii?8{m_`HYDYM>tY4RinzjpzaX0F6K$P=V*(1dbFG6zuf-{Yyfj&=hpR zmWsUDoKEKq3@QgWh(SHzK{f$hI>^!jnv9=74c>bdpFT|9(!9L938rT)H#c`2I=lcY zAOJkX_phTs*hpvt-T`NUExant)xzQM7>LY#bg-C3vpvbA#S;1Nz%{JM%|%5;6HP&$ z!{L~QPPu?e-jgCGM-NNJ84_)p{G_cqbYLY!^*TD!k^;p2k8a>K`fnfg)kqsMUhV+i z@{C4O7)?+%rvib%9GzyCwpLhJI32Tgl+A}onxGS7x=$jVPy+FyY|Pf>6xr+a?2VNm zag7GsQ9Xffw|gF5aMuK8Nuo)7KzX{l%i&eVV96xL0Uk%2qZfXV!I0u`es4$`q2y$_m-5D zjMq#cHqzyIz^gQ{^NgaFd}vJs;vc4h`3n|N8frY2a+k|BTML2P*0 z4>Y(3iWZK878X0uh#e@Nh(S+8d|~6hutZ~wL}Mf#s2Lvy!~-QFZ7?D&XCemAMC4S? z;HjLP3v0roA7W`e<+334~a_}v)g?W`6dYw+zXaI=5d+ga2&>r(Cq>bW1--@x=% z{P7nd?H_(oZlLNbzIMBPGNtBAptTp2nRO#ZxH$bQXM}a%;B*HHWLy}XeUoxEfv$D7#@_~dKE^oIUCg*pjpR&Hj?O& zk%~b*CMTUkX3YjfzJtl4LXK5*G>d1S;G+pT>&{AAaWN?Ys;>P7!3`nZXQZ_#00000 LNkvXXu0mjf9}qT( literal 0 HcmV?d00001 diff --git a/libs/EnhancedListView/src/main/res/drawable-ldpi/elv_ic_action_undo.png b/libs/EnhancedListView/src/main/res/drawable-ldpi/elv_ic_action_undo.png new file mode 100644 index 0000000000000000000000000000000000000000..4a9714756f33671c9a694ab40e6fcd1486bf3b9b GIT binary patch literal 3104 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 A4*&oF literal 0 HcmV?d00001 diff --git a/libs/EnhancedListView/src/main/res/drawable-xhdpi/elv_ic_action_undo.png b/libs/EnhancedListView/src/main/res/drawable-xhdpi/elv_ic_action_undo.png new file mode 100644 index 0000000000000000000000000000000000000000..2b7996036c57ce463b85a367c6d0d46a0c68c2b4 GIT binary patch literal 1068 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 Tofj}+qi}$q57k?6gwFa4nvD~b literal 0 HcmV?d00001 diff --git a/libs/EnhancedListView/src/main/res/drawable-xxhdpi/elv_toast_frame.9.png b/libs/EnhancedListView/src/main/res/drawable-xxhdpi/elv_toast_frame.9.png new file mode 100644 index 0000000000000000000000000000000000000000..99efd3fe99ef6e35f8c07a744cff56d093198b6f GIT binary patch literal 1979 zcmV;s2SoUZP)gwtdPD=2u3C}$m$~Ic}YuXlVleSIUNSGU- zXrmgf!Z9&RUtiw_dAc1IG4Eln!2G3}sdhLh{Mc=ah&L=E&S0d-%&3N{C621!rEZde z^N@T#!#LJ>CI&pJ+TyT^(%;{|M#MQJ;!I-{XH2Wcsx^*Vddlq(?qCdWsMb4Dy#^=$ zfu}J-GN|^cTMLv%e!qGWNrG;oq2H!PFp zeXx(>Cz~gq&+qk|0Rj;-XEau=V7zu|$e=W6k)SpWiyAY>nK15*r|U)1h#v@Kb0X$D z<#5ffTJGz)+{l%~&;_ z$*sGExKM}N0bi+=fK(LVyw1+fH`(D2VNNXQ?d{zd+j#OtM-UT2awL}V zdU|@^mM?jVkPwombqkUORo=Ib8YzI+!QX^fyRBLQEfOrs-fl$U9 z9v*%}{^%~kK}_zdmjWA4`sba92A0NpI|Ca}{@@IvfhEoaGM>pmd5&OUi5DwmuB>Rh zB4UAMigk_G)zx(Yp}=y=)P{$rW_7vowjm05nqX{yX*?P9?M4*vG{M;Y(s(kXJ%A|SDL?N(x$%x6 z3V1T!vGOq zDT5>?7F#)(5q$G-#e*3+iWVGqv!^+H8AqF$* zq0q)NW4$2+gotCip&HhzZ9Msc&B9ZJgpjCbHb*udPd#w(v+kPhBVT$8VWAGU)hpJFXP$Z)goQd|yG%ek1vj2anHoi4 zsM5ubhOkmNuuwGP zWwY6J@}rXopL!GKWEEpzKmTT2;ldD2nfK8;3X#L*<8v5HA9m?GebGZph?fAbYR6IV|H zC-{l+Q^mQ&=_=+dzB?Gf8+YU%{)0Bk(wd)c1m?s7w>$h*V~1;V&0BP*l8AG(z!j!8 znKPPh!eRBtfA5G`!=%>7OHEe|RZAQdA>ly8J1ipJ!$^U-ra~ z3fw%j67yoV-&G^k3dguHp&k~wU$`VJq}Xf=MuYuz4nd^YF1cK8i-_JW|Ls4gWI=k$ zw6$B?jsqH_wCg@k3Cw50NeRw%;dv9AOzVDK+oEkUdsy2EbCawOO2FRN*Frkrmd|hH z^B1!> + + + + \ No newline at end of file diff --git a/libs/EnhancedListView/src/main/res/drawable/elv_undo_btn_bg.xml b/libs/EnhancedListView/src/main/res/drawable/elv_undo_btn_bg.xml new file mode 100644 index 000000000..fde26ad09 --- /dev/null +++ b/libs/EnhancedListView/src/main/res/drawable/elv_undo_btn_bg.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/libs/EnhancedListView/src/main/res/drawable/elv_undo_btn_bg_focused.xml b/libs/EnhancedListView/src/main/res/drawable/elv_undo_btn_bg_focused.xml new file mode 100644 index 000000000..d3c63634a --- /dev/null +++ b/libs/EnhancedListView/src/main/res/drawable/elv_undo_btn_bg_focused.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/libs/EnhancedListView/src/main/res/drawable/elv_undo_btn_bg_pressed.xml b/libs/EnhancedListView/src/main/res/drawable/elv_undo_btn_bg_pressed.xml new file mode 100644 index 000000000..ca09bdb8a --- /dev/null +++ b/libs/EnhancedListView/src/main/res/drawable/elv_undo_btn_bg_pressed.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/libs/EnhancedListView/src/main/res/layout-v19/elv_undo_popup.xml b/libs/EnhancedListView/src/main/res/layout-v19/elv_undo_popup.xml new file mode 100644 index 000000000..23081757d --- /dev/null +++ b/libs/EnhancedListView/src/main/res/layout-v19/elv_undo_popup.xml @@ -0,0 +1,43 @@ + + + + + + + +