kopia lustrzana https://github.com/ryukoposting/Signal-Android
Improve responsiveness of archive animations, other swipe tweaks.
rodzic
8c45600365
commit
6499ed4637
|
@ -125,6 +125,7 @@ public class ConversationListArchiveFragment extends ConversationListFragment im
|
||||||
@Override
|
@Override
|
||||||
protected void onItemSwiped(long threadId, int unreadCount) {
|
protected void onItemSwiped(long threadId, int unreadCount) {
|
||||||
archiveDecoration.onArchiveStarted();
|
archiveDecoration.onArchiveStarted();
|
||||||
|
itemAnimator.enable();
|
||||||
|
|
||||||
new SnackbarAsyncTask<Long>(getViewLifecycleOwner().getLifecycle(),
|
new SnackbarAsyncTask<Long>(getViewLifecycleOwner().getLifecycle(),
|
||||||
requireView(),
|
requireView(),
|
||||||
|
|
|
@ -157,6 +157,7 @@ import org.thoughtcrime.securesms.util.views.Stub;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState;
|
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -214,6 +215,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||||
private SignalBottomActionBar bottomActionBar;
|
private SignalBottomActionBar bottomActionBar;
|
||||||
|
|
||||||
protected ConversationListArchiveItemDecoration archiveDecoration;
|
protected ConversationListArchiveItemDecoration archiveDecoration;
|
||||||
|
protected ConversationListItemAnimator itemAnimator;
|
||||||
private Stopwatch startupStopwatch;
|
private Stopwatch startupStopwatch;
|
||||||
|
|
||||||
public static ConversationListFragment newInstance() {
|
public static ConversationListFragment newInstance() {
|
||||||
|
@ -272,9 +274,10 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||||
cameraFab.show();
|
cameraFab.show();
|
||||||
|
|
||||||
archiveDecoration = new ConversationListArchiveItemDecoration(new ColorDrawable(getResources().getColor(R.color.conversation_list_archive_background_end)));
|
archiveDecoration = new ConversationListArchiveItemDecoration(new ColorDrawable(getResources().getColor(R.color.conversation_list_archive_background_end)));
|
||||||
|
itemAnimator = new ConversationListItemAnimator();
|
||||||
|
|
||||||
list.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
list.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||||
list.setItemAnimator(new ConversationListItemAnimator());
|
list.setItemAnimator(itemAnimator);
|
||||||
list.addOnScrollListener(new ScrollListener());
|
list.addOnScrollListener(new ScrollListener());
|
||||||
list.addItemDecoration(archiveDecoration);
|
list.addItemDecoration(archiveDecoration);
|
||||||
|
|
||||||
|
@ -1276,6 +1279,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
protected void onItemSwiped(long threadId, int unreadCount) {
|
protected void onItemSwiped(long threadId, int unreadCount) {
|
||||||
archiveDecoration.onArchiveStarted();
|
archiveDecoration.onArchiveStarted();
|
||||||
|
itemAnimator.enable();
|
||||||
|
|
||||||
new SnackbarAsyncTask<Long>(getViewLifecycleOwner().getLifecycle(),
|
new SnackbarAsyncTask<Long>(getViewLifecycleOwner().getLifecycle(),
|
||||||
requireView(),
|
requireView(),
|
||||||
|
@ -1315,7 +1319,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||||
ApplicationDependencies.getMessageNotifier().updateNotification(context);
|
ApplicationDependencies.getMessageNotifier().updateNotification(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, threadId);
|
}.executeOnExecutor(SignalExecutors.BOUNDED, threadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PaymentNotificationListener implements UnreadPaymentsView.Listener {
|
private class PaymentNotificationListener implements UnreadPaymentsView.Listener {
|
||||||
|
@ -1358,14 +1362,18 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||||
|
|
||||||
private class ArchiveListenerCallback extends ItemTouchHelper.SimpleCallback {
|
private class ArchiveListenerCallback extends ItemTouchHelper.SimpleCallback {
|
||||||
|
|
||||||
|
private static final long SWIPE_ANIMATION_DURATION = 175;
|
||||||
|
|
||||||
private static final float MIN_ICON_SCALE = 0.85f;
|
private static final float MIN_ICON_SCALE = 0.85f;
|
||||||
private static final float MAX_ICON_SCALE = 1f;
|
private static final float MAX_ICON_SCALE = 1f;
|
||||||
|
|
||||||
private final int archiveColorStart;
|
private final int archiveColorStart;
|
||||||
private final int archiveColorEnd;
|
private final int archiveColorEnd;
|
||||||
|
|
||||||
|
private WeakReference<RecyclerView.ViewHolder> lastTouched;
|
||||||
|
|
||||||
ArchiveListenerCallback(@ColorInt int archiveColorStart, @ColorInt int archiveColorEnd) {
|
ArchiveListenerCallback(@ColorInt int archiveColorStart, @ColorInt int archiveColorEnd) {
|
||||||
super(0, ItemTouchHelper.RIGHT);
|
super(0, ItemTouchHelper.END);
|
||||||
this.archiveColorStart = archiveColorStart;
|
this.archiveColorStart = archiveColorStart;
|
||||||
this.archiveColorEnd = archiveColorEnd;
|
this.archiveColorEnd = archiveColorEnd;
|
||||||
}
|
}
|
||||||
|
@ -1389,13 +1397,33 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastTouched = new WeakReference<>(viewHolder);
|
||||||
|
|
||||||
return super.getSwipeDirs(recyclerView, viewHolder);
|
return super.getSwipeDirs(recyclerView, viewHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
@Override
|
@Override
|
||||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
||||||
if (viewHolder.itemView instanceof ConversationListItemInboxZero) return;
|
if (lastTouched != null) {
|
||||||
|
Log.w(TAG, "Falling back to slower onSwiped() event.");
|
||||||
|
onTrueSwipe(viewHolder);
|
||||||
|
lastTouched = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getAnimationDuration(@NonNull RecyclerView recyclerView, int animationType, float animateDx, float animateDy) {
|
||||||
|
if (animationType == ItemTouchHelper.ANIMATION_TYPE_SWIPE_SUCCESS && lastTouched != null && lastTouched.get() != null) {
|
||||||
|
onTrueSwipe(lastTouched.get());
|
||||||
|
lastTouched = null;
|
||||||
|
} else if (animationType == ItemTouchHelper.ANIMATION_TYPE_SWIPE_CANCEL) {
|
||||||
|
lastTouched = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SWIPE_ANIMATION_DURATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onTrueSwipe(RecyclerView.ViewHolder viewHolder) {
|
||||||
final long threadId = ((ConversationListItem)viewHolder.itemView).getThreadId();
|
final long threadId = ((ConversationListItem)viewHolder.itemView).getThreadId();
|
||||||
final int unreadCount = ((ConversationListItem)viewHolder.itemView).getUnreadCount();
|
final int unreadCount = ((ConversationListItem)viewHolder.itemView).getUnreadCount();
|
||||||
|
|
||||||
|
@ -1409,24 +1437,26 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||||
boolean isCurrentlyActive)
|
boolean isCurrentlyActive)
|
||||||
{
|
{
|
||||||
if (viewHolder.itemView instanceof ConversationListItemInboxZero) return;
|
if (viewHolder.itemView instanceof ConversationListItemInboxZero) return;
|
||||||
|
float absoluteDx = Math.abs(dX);
|
||||||
|
|
||||||
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
|
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
|
||||||
Resources resources = getResources();
|
Resources resources = getResources();
|
||||||
View itemView = viewHolder.itemView;
|
View itemView = viewHolder.itemView;
|
||||||
float percentDx = Math.abs(dX) / viewHolder.itemView.getWidth();
|
float percentDx = absoluteDx / viewHolder.itemView.getWidth();
|
||||||
int color = ArgbEvaluatorCompat.getInstance().evaluate(Math.min(1f, percentDx * (1 / 0.25f)), archiveColorStart, archiveColorEnd);
|
int color = ArgbEvaluatorCompat.getInstance().evaluate(Math.min(1f, percentDx * (1 / 0.25f)), archiveColorStart, archiveColorEnd);
|
||||||
float scaleStartPoint = DimensionUnit.DP.toPixels(48f);
|
float scaleStartPoint = DimensionUnit.DP.toPixels(48f);
|
||||||
float scaleEndPoint = DimensionUnit.DP.toPixels(96f);
|
float scaleEndPoint = DimensionUnit.DP.toPixels(96f);
|
||||||
|
|
||||||
float scale;
|
float scale;
|
||||||
if (dX < scaleStartPoint) {
|
if (absoluteDx < scaleStartPoint) {
|
||||||
scale = MIN_ICON_SCALE;
|
scale = MIN_ICON_SCALE;
|
||||||
} else if (dX > scaleEndPoint) {
|
} else if (absoluteDx > scaleEndPoint) {
|
||||||
scale = MAX_ICON_SCALE;
|
scale = MAX_ICON_SCALE;
|
||||||
} else {
|
} else {
|
||||||
scale = Math.min(MAX_ICON_SCALE, MIN_ICON_SCALE + ((dX - scaleStartPoint) / (scaleEndPoint - scaleStartPoint)) * (MAX_ICON_SCALE - MIN_ICON_SCALE));
|
scale = Math.min(MAX_ICON_SCALE, MIN_ICON_SCALE + ((absoluteDx - scaleStartPoint) / (scaleEndPoint - scaleStartPoint)) * (MAX_ICON_SCALE - MIN_ICON_SCALE));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dX > 0) {
|
if (absoluteDx > 0) {
|
||||||
if (archiveDrawable == null) {
|
if (archiveDrawable == null) {
|
||||||
archiveDrawable = Objects.requireNonNull(AppCompatResources.getDrawable(requireContext(), getArchiveIconRes()));
|
archiveDrawable = Objects.requireNonNull(AppCompatResources.getDrawable(requireContext(), getArchiveIconRes()));
|
||||||
archiveDrawable.setColorFilter(new SimpleColorFilter(Color.WHITE));
|
archiveDrawable.setColorFilter(new SimpleColorFilter(Color.WHITE));
|
||||||
|
@ -1441,8 +1471,13 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||||
float gutter = resources.getDimension(R.dimen.dsl_settings_gutter);
|
float gutter = resources.getDimension(R.dimen.dsl_settings_gutter);
|
||||||
float extra = resources.getDimension(R.dimen.conversation_list_fragment_archive_padding);
|
float extra = resources.getDimension(R.dimen.conversation_list_fragment_archive_padding);
|
||||||
|
|
||||||
|
if (ViewUtil.isLtr(requireContext())) {
|
||||||
canvas.translate(itemView.getLeft() + gutter + extra,
|
canvas.translate(itemView.getLeft() + gutter + extra,
|
||||||
itemView.getTop() + (itemView.getBottom() - itemView.getTop() - archiveDrawable.getIntrinsicHeight()) / 2f);
|
itemView.getTop() + (itemView.getBottom() - itemView.getTop() - archiveDrawable.getIntrinsicHeight()) / 2f);
|
||||||
|
} else {
|
||||||
|
canvas.translate(itemView.getRight() - gutter - extra,
|
||||||
|
itemView.getTop() + (itemView.getBottom() - itemView.getTop() - archiveDrawable.getIntrinsicHeight()) / 2f);
|
||||||
|
}
|
||||||
|
|
||||||
canvas.scale(scale, scale, archiveDrawable.getIntrinsicWidth() / 2f, archiveDrawable.getIntrinsicHeight() / 2f);
|
canvas.scale(scale, scale, archiveDrawable.getIntrinsicWidth() / 2f, archiveDrawable.getIntrinsicHeight() / 2f);
|
||||||
|
|
||||||
|
@ -1450,7 +1485,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
|
|
||||||
ViewCompat.setElevation(viewHolder.itemView, DimensionUnit.DP.toPixels(4f));
|
ViewCompat.setElevation(viewHolder.itemView, DimensionUnit.DP.toPixels(4f));
|
||||||
} else if (dX == 0) {
|
} else if (absoluteDx == 0) {
|
||||||
ViewCompat.setElevation(viewHolder.itemView, DimensionUnit.DP.toPixels(0f));
|
ViewCompat.setElevation(viewHolder.itemView, DimensionUnit.DP.toPixels(0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1464,6 +1499,8 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||||
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||||
super.clearView(recyclerView, viewHolder);
|
super.clearView(recyclerView, viewHolder);
|
||||||
ViewCompat.setElevation(viewHolder.itemView, 0);
|
ViewCompat.setElevation(viewHolder.itemView, 0);
|
||||||
|
lastTouched = null;
|
||||||
|
itemAnimator.postDisable(requireView().getHandler());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,42 @@
|
||||||
package org.thoughtcrime.securesms.conversationlist;
|
package org.thoughtcrime.securesms.conversationlist;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
|
||||||
|
import androidx.annotation.MainThread;
|
||||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
|
||||||
public class ConversationListItemAnimator extends DefaultItemAnimator {
|
public class ConversationListItemAnimator extends DefaultItemAnimator {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(ConversationListItemAnimator.class);
|
||||||
|
|
||||||
|
private static final long ANIMATION_DURATION = 200;
|
||||||
|
|
||||||
|
private boolean shouldDisable;
|
||||||
|
|
||||||
public ConversationListItemAnimator() {
|
public ConversationListItemAnimator() {
|
||||||
setSupportsChangeAnimations(false);
|
setSupportsChangeAnimations(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean animateRemove(RecyclerView.ViewHolder holder) {
|
@MainThread
|
||||||
return super.animateRemove(holder);
|
public void enable() {
|
||||||
|
setMoveDuration(ANIMATION_DURATION);
|
||||||
|
shouldDisable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need to reasonable ensure that the animation has started before we disable things here, so we add a slight delay.
|
||||||
|
*/
|
||||||
|
@MainThread
|
||||||
|
public void postDisable(Handler handler) {
|
||||||
|
shouldDisable = true;
|
||||||
|
handler.postDelayed(() -> {
|
||||||
|
if (shouldDisable) {
|
||||||
|
setMoveDuration(0);
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Disable was canceled by an enable.");
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
|
||||||
|
<solid android:color="@color/signal_background_primary"/>
|
||||||
|
<corners
|
||||||
|
android:topRightRadius="8dp"
|
||||||
|
android:bottomRightRadius="8dp"/>
|
||||||
|
<padding android:bottom="5dp" android:left="5dp" android:right="5dp" android:top="5dp"/>
|
||||||
|
</shape>
|
|
@ -1,6 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
|
||||||
<solid android:color="@color/signal_background_primary"/>
|
<solid android:color="@color/signal_background_primary"/>
|
||||||
<corners android:radius="8dp"/>
|
<corners
|
||||||
|
android:topLeftRadius="8dp"
|
||||||
|
android:bottomLeftRadius="8dp"/>
|
||||||
<padding android:bottom="5dp" android:left="5dp" android:right="5dp" android:top="5dp"/>
|
<padding android:bottom="5dp" android:left="5dp" android:right="5dp" android:top="5dp"/>
|
||||||
</shape>
|
</shape>
|
Ładowanie…
Reference in New Issue