Fix several beta issues with new slide animations.

fork-5.53.8
Alex Hart 2021-10-25 13:39:01 -03:00 zatwierdzone przez GitHub
rodzic ced05fe579
commit 98fce53cf1
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
8 zmienionych plików z 149 dodań i 97 usunięć

Wyświetl plik

@ -20,7 +20,7 @@ class BodyBubbleLayoutTransition(bodyBubble: ConversationItemBodyBubble) : Layou
animator.duration = getAnimator(CHANGE_DISAPPEARING).duration animator.duration = getAnimator(CHANGE_DISAPPEARING).duration
animator.addUpdateListener { animator.addUpdateListener {
val parentRecycler: RecyclerView? = bodyBubble.parent.parent as? RecyclerView val parentRecycler: RecyclerView? = bodyBubble.parent?.parent as? RecyclerView
try { try {
parentRecycler?.invalidate() parentRecycler?.invalidate()

Wyświetl plik

@ -166,7 +166,7 @@ public class ConversationAdapter
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
if (hasHeader() && position == 0) { if (isTypingViewEnabled() && position == 0) {
return MESSAGE_TYPE_HEADER; return MESSAGE_TYPE_HEADER;
} }
@ -365,7 +365,7 @@ public class ConversationAdapter
} }
public @Nullable ConversationMessage getItem(int position) { public @Nullable ConversationMessage getItem(int position) {
position = hasHeader() ? position - 1 : position; position = isTypingViewEnabled() ? position - 1 : position;
if (position == -1) { if (position == -1) {
return null; return null;
@ -421,7 +421,7 @@ public class ConversationAdapter
*/ */
@MainThread @MainThread
int getAdapterPositionForMessagePosition(int messagePosition) { int getAdapterPositionForMessagePosition(int messagePosition) {
return hasHeader() ? messagePosition + 1 : messagePosition; return isTypingViewEnabled() ? messagePosition + 1 : messagePosition;
} }
/** /**
@ -602,7 +602,7 @@ public class ConversationAdapter
} }
} }
public boolean hasHeader() { public boolean isTypingViewEnabled() {
return isTypingViewEnabled; return isTypingViewEnabled;
} }
@ -611,7 +611,7 @@ public class ConversationAdapter
} }
private boolean isHeaderPosition(int position) { private boolean isHeaderPosition(int position) {
return hasHeader() && position == 0; return isTypingViewEnabled() && position == 0;
} }
private boolean isFooterPosition(int position) { private boolean isFooterPosition(int position) {

Wyświetl plik

@ -5,7 +5,7 @@ import androidx.annotation.NonNull;
/** /**
* Represents metadata about a conversation. * Represents metadata about a conversation.
*/ */
final class ConversationData { public final class ConversationData {
private final long threadId; private final long threadId;
private final long lastSeen; private final long lastSeen;
private final int lastSeenPosition; private final int lastSeenPosition;

Wyświetl plik

@ -53,9 +53,7 @@ import androidx.core.app.ActivityOptionsCompat;
import androidx.core.text.HtmlCompat; import androidx.core.text.HtmlCompat;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModelProviders; import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -86,7 +84,7 @@ import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickList
import org.thoughtcrime.securesms.conversation.ConversationAdapter.StickyHeaderViewHolder; import org.thoughtcrime.securesms.conversation.ConversationAdapter.StickyHeaderViewHolder;
import org.thoughtcrime.securesms.conversation.colors.Colorizer; import org.thoughtcrime.securesms.conversation.colors.Colorizer;
import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer; import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer;
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectItemAnimator; import org.thoughtcrime.securesms.conversation.mutiselect.ConversationItemAnimator;
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectItemDecoration; import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectItemDecoration;
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart; import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart;
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment; import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment;
@ -158,7 +156,6 @@ import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.WindowUtil; import org.thoughtcrime.securesms.util.WindowUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.views.AdaptiveActionsToolbar; import org.thoughtcrime.securesms.util.views.AdaptiveActionsToolbar;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
@ -223,8 +220,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
private ConversationUpdateTick conversationUpdateTick; private ConversationUpdateTick conversationUpdateTick;
private MultiselectItemDecoration multiselectItemDecoration; private MultiselectItemDecoration multiselectItemDecoration;
private boolean initialDataLoaded = false;
public static void prepare(@NonNull Context context) { public static void prepare(@NonNull Context context) {
FrameLayout parent = new FrameLayout(context); FrameLayout parent = new FrameLayout(context);
parent.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)); parent.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT));
@ -259,14 +254,14 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
reactionsShade = view.findViewById(R.id.reactions_shade); reactionsShade = view.findViewById(R.id.reactions_shade);
final LinearLayoutManager layoutManager = new SmoothScrollingLinearLayoutManager(getActivity(), true); final LinearLayoutManager layoutManager = new SmoothScrollingLinearLayoutManager(getActivity(), true);
final MultiselectItemAnimator multiselectItemAnimator = new MultiselectItemAnimator(() -> { final ConversationItemAnimator conversationItemAnimator = new ConversationItemAnimator(() -> {
ConversationAdapter adapter = getListAdapter(); ConversationAdapter adapter = getListAdapter();
if (adapter == null) { if (adapter == null) {
return false; return false;
} else { } else {
return Util.hasItems(adapter.getSelectedItems()); return Util.hasItems(adapter.getSelectedItems());
} }
}, () -> !initialDataLoaded, () -> list.canScrollVertically(1) || list.canScrollVertically(-1)); }, () -> conversationViewModel.shouldPlayMessageAnimations(), () -> list.canScrollVertically(1) || list.canScrollVertically(-1));
multiselectItemDecoration = new MultiselectItemDecoration(requireContext(), multiselectItemDecoration = new MultiselectItemDecoration(requireContext(),
() -> conversationViewModel.getWallpaper().getValue()); () -> conversationViewModel.getWallpaper().getValue());
@ -276,7 +271,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
RecyclerViewColorizer recyclerViewColorizer = new RecyclerViewColorizer(list); RecyclerViewColorizer recyclerViewColorizer = new RecyclerViewColorizer(list);
list.addItemDecoration(multiselectItemDecoration); list.addItemDecoration(multiselectItemDecoration);
list.setItemAnimator(multiselectItemAnimator); list.setItemAnimator(conversationItemAnimator);
getViewLifecycleOwner().getLifecycle().addObserver(multiselectItemDecoration); getViewLifecycleOwner().getLifecycle().addObserver(multiselectItemDecoration);
@ -307,25 +302,12 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
this.messageCountsViewModel = ViewModelProviders.of(requireActivity()).get(MessageCountsViewModel.class); this.messageCountsViewModel = ViewModelProviders.of(requireActivity()).get(MessageCountsViewModel.class);
this.conversationViewModel = ViewModelProviders.of(requireActivity(), new ConversationViewModel.Factory()).get(ConversationViewModel.class); this.conversationViewModel = ViewModelProviders.of(requireActivity(), new ConversationViewModel.Factory()).get(ConversationViewModel.class);
MutableLiveData<Boolean> hasSubmittedNonEmptyList = new MutableLiveData<>(false);
LiveData<Boolean> hasMessagesInThread = Transformations.map(conversationViewModel.getConversationMetadata(), c -> c.getThreadSize() > 0);
LiveData<Boolean> playAnimations = LiveDataUtil.combineLatest(hasSubmittedNonEmptyList, hasMessagesInThread, (a, b) -> {
if (a && b) {
return true;
} else {
return !b;
}
});
playAnimations.observe(getViewLifecycleOwner(), p -> initialDataLoaded = p);
conversationViewModel.getChatColors().observe(getViewLifecycleOwner(), recyclerViewColorizer::setChatColors); conversationViewModel.getChatColors().observe(getViewLifecycleOwner(), recyclerViewColorizer::setChatColors);
conversationViewModel.getMessages().observe(getViewLifecycleOwner(), messages -> { conversationViewModel.getMessages().observe(getViewLifecycleOwner(), messages -> {
ConversationAdapter adapter = getListAdapter(); ConversationAdapter adapter = getListAdapter();
if (adapter != null) { if (adapter != null) {
getListAdapter().submitList(messages, () -> { getListAdapter().submitList(messages, () -> {
hasSubmittedNonEmptyList.postValue(!messages.isEmpty()); list.post(() -> conversationViewModel.onMessagesCommitted(messages));
}); });
} }
}); });
@ -391,7 +373,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
public void clearFocusedItem() { public void clearFocusedItem() {
multiselectItemDecoration.setFocusedItem(null); multiselectItemDecoration.setFocusedItem(null);
list.invalidateItemDecorations(); list.invalidateItemDecorations();
reactionsShade.setVisibility(View.GONE); reactionsShade.setVisibility(View.INVISIBLE);
} }
private void updateConversationItemTimestamps() { private void updateConversationItemTimestamps() {
@ -1040,7 +1022,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
.submit(); .submit();
} else if (conversation.getMessageRequestData().isMessageRequestAccepted()) { } else if (conversation.getMessageRequestData().isMessageRequestAccepted()) {
snapToTopDataObserver.buildScrollPosition(conversation.shouldScrollToLastSeen() ? lastSeenPosition : lastScrolledPosition) snapToTopDataObserver.buildScrollPosition(conversation.shouldScrollToLastSeen() ? lastSeenPosition : lastScrolledPosition)
.withOnPerformScroll((layoutManager, position) -> layoutManager.scrollToPositionWithOffset(position, list.getHeight() - (conversation.shouldScrollToLastSeen() ? lastSeenScrollOffset : 0))) .withOnPerformScroll((layoutManager, position) -> layoutManager.scrollToPositionWithOffset(position, (list.getHeight() + reactionsShade.getHeight()) - (conversation.shouldScrollToLastSeen() ? lastSeenScrollOffset : 0)))
.withOnScrollRequestComplete(afterScroll) .withOnScrollRequestComplete(afterScroll)
.submit(); .submit();
} else { } else {
@ -1064,7 +1046,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
} }
private boolean isTypingIndicatorShowing() { private boolean isTypingIndicatorShowing() {
return getListAdapter().hasHeader(); return getListAdapter().isTypingViewEnabled();
} }
public void onSearchQueryUpdated(@Nullable String query) { public void onSearchQueryUpdated(@Nullable String query) {

Wyświetl plik

@ -6,6 +6,7 @@ import androidx.annotation.MainThread;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.Transformations; import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
@ -37,8 +38,10 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.DefaultValueLiveData; import org.thoughtcrime.securesms.util.DefaultValueLiveData;
import org.thoughtcrime.securesms.util.SingleLiveEvent; import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import org.thoughtcrime.securesms.util.livedata.Store;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
@ -76,6 +79,8 @@ public class ConversationViewModel extends ViewModel {
private final MutableLiveData<Integer> toolbarBottom; private final MutableLiveData<Integer> toolbarBottom;
private final MutableLiveData<Integer> inlinePlayerHeight; private final MutableLiveData<Integer> inlinePlayerHeight;
private final LiveData<Integer> conversationTopMargin; private final LiveData<Integer> conversationTopMargin;
private final Store<ThreadAnimationState> threadAnimationStateStore;
private final Observer<ThreadAnimationState> threadAnimationStateStoreDriver;
private final Map<GroupId, Set<Recipient>> sessionMemberCache = new HashMap<>(); private final Map<GroupId, Set<Recipient>> sessionMemberCache = new HashMap<>();
@ -99,6 +104,7 @@ public class ConversationViewModel extends ViewModel {
this.toolbarBottom = new MutableLiveData<>(); this.toolbarBottom = new MutableLiveData<>();
this.inlinePlayerHeight = new MutableLiveData<>(); this.inlinePlayerHeight = new MutableLiveData<>();
this.conversationTopMargin = Transformations.distinctUntilChanged(LiveDataUtil.combineLatest(toolbarBottom, inlinePlayerHeight, Integer::sum)); this.conversationTopMargin = Transformations.distinctUntilChanged(LiveDataUtil.combineLatest(toolbarBottom, inlinePlayerHeight, Integer::sum));
this.threadAnimationStateStore = new Store<>(new ThreadAnimationState(-1L, null, false));
LiveData<Recipient> recipientLiveData = LiveDataUtil.mapAsync(recipientId, Recipient::resolved); LiveData<Recipient> recipientLiveData = LiveDataUtil.mapAsync(recipientId, Recipient::resolved);
LiveData<ThreadAndRecipient> threadAndRecipient = LiveDataUtil.combineLatest(threadId, recipientLiveData, ThreadAndRecipient::new); LiveData<ThreadAndRecipient> threadAndRecipient = LiveDataUtil.combineLatest(threadId, recipientLiveData, ThreadAndRecipient::new);
@ -158,6 +164,41 @@ public class ConversationViewModel extends ViewModel {
chatColors = LiveDataUtil.mapDistinct(Transformations.switchMap(recipientId, chatColors = LiveDataUtil.mapDistinct(Transformations.switchMap(recipientId,
id -> Recipient.live(id).getLiveData()), id -> Recipient.live(id).getLiveData()),
Recipient::getChatColors); Recipient::getChatColors);
threadAnimationStateStore.update(threadId, (id, state) -> {
if (state.getThreadId() == id) {
return state;
} else {
return new ThreadAnimationState(id, null, false);
}
});
threadAnimationStateStore.update(metadata, (m, state) -> {
if (state.getThreadId() == m.getThreadId()) {
return state.copy(state.getThreadId(), m, state.getHasCommittedNonEmptyMessageList());
} else {
return state.copy(m.getThreadId(), m, false);
}
});
this.threadAnimationStateStoreDriver = state -> {};
threadAnimationStateStore.getStateLiveData().observeForever(threadAnimationStateStoreDriver);
}
void onMessagesCommitted(@NonNull List<ConversationMessage> conversationMessages) {
if (Util.hasItems(conversationMessages)) {
threadAnimationStateStore.update(state -> {
if (state.getThreadId() == conversationMessages.get(0).getMessageRecord().getThreadId()) {
return state.copy(state.getThreadId(), state.getThreadMetadata(), true);
} else {
return state;
}
});
}
}
boolean shouldPlayMessageAnimations() {
return threadAnimationStateStore.getState().shouldPlayMessageAnimations();
} }
void setToolbarBottom(int bottom) { void setToolbarBottom(int bottom) {
@ -301,6 +342,7 @@ public class ConversationViewModel extends ViewModel {
@Override @Override
protected void onCleared() { protected void onCleared() {
super.onCleared(); super.onCleared();
threadAnimationStateStore.getStateLiveData().removeObserver(threadAnimationStateStoreDriver);
ApplicationDependencies.getDatabaseObserver().unregisterObserver(conversationObserver); ApplicationDependencies.getDatabaseObserver().unregisterObserver(conversationObserver);
ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageUpdateObserver); ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageUpdateObserver);
ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageInsertObserver); ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageInsertObserver);

Wyświetl plik

@ -0,0 +1,19 @@
package org.thoughtcrime.securesms.conversation
/**
* Represents how conversation bubbles should animate at any given time.
*/
data class ThreadAnimationState constructor(
val threadId: Long,
val threadMetadata: ConversationData?,
val hasCommittedNonEmptyMessageList: Boolean
) {
fun shouldPlayMessageAnimations(): Boolean {
return when {
threadId == -1L || threadMetadata == null -> false
threadMetadata.threadSize == 0 -> true
threadMetadata.threadSize > 0 && hasCommittedNonEmptyMessageList -> true
else -> false
}
}
}

Wyświetl plik

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.conversation.mutiselect package org.thoughtcrime.securesms.conversation.mutiselect
import android.animation.ObjectAnimator import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ValueAnimator import android.animation.ValueAnimator
import androidx.core.animation.doOnEnd import androidx.core.animation.doOnEnd
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -12,9 +13,9 @@ import org.thoughtcrime.securesms.conversation.ConversationAdapter
* *
* Can be expanded upon in the future to animate other things, such as message sends. * Can be expanded upon in the future to animate other things, such as message sends.
*/ */
class MultiselectItemAnimator( class ConversationItemAnimator(
private val isInMultiSelectMode: () -> Boolean, private val isInMultiSelectMode: () -> Boolean,
private val isLoadingInitialContent: () -> Boolean, private val shouldPlayMessageAnimations: () -> Boolean,
private val isParentFilled: () -> Boolean private val isParentFilled: () -> Boolean
) : RecyclerView.ItemAnimator() { ) : RecyclerView.ItemAnimator() {
@ -23,14 +24,30 @@ class MultiselectItemAnimator(
CHANGE CHANGE
} }
private val pendingSlideAnimations: MutableSet<RecyclerView.ViewHolder> = mutableSetOf() private data class TweeningInfo(
private var pendingTypingViewSlideOut: RecyclerView.ViewHolder? = null val startValue: Float,
val endValue: Float
) {
fun lerp(progress: Float): Float {
return startValue + progress * (endValue - startValue)
}
}
private val slideAnimations: MutableMap<RecyclerView.ViewHolder, ValueAnimator> = mutableMapOf() private data class AnimationInfo(
val sharedAnimator: ValueAnimator,
val tweeningInfo: TweeningInfo
)
private val pendingSlideAnimations: MutableMap<RecyclerView.ViewHolder, TweeningInfo> = mutableMapOf()
private val slideAnimations: MutableMap<RecyclerView.ViewHolder, AnimationInfo> = mutableMapOf()
override fun animateDisappearance(viewHolder: RecyclerView.ViewHolder, preLayoutInfo: ItemHolderInfo, postLayoutInfo: ItemHolderInfo?): Boolean { override fun animateDisappearance(viewHolder: RecyclerView.ViewHolder, preLayoutInfo: ItemHolderInfo, postLayoutInfo: ItemHolderInfo?): Boolean {
if (viewHolder is ConversationAdapter.HeaderViewHolder && pendingTypingViewSlideOut == null) { if (viewHolder is ConversationAdapter.HeaderViewHolder &&
pendingTypingViewSlideOut = viewHolder !pendingSlideAnimations.containsKey(viewHolder) &&
!slideAnimations.containsKey(viewHolder) &&
shouldPlayMessageAnimations()
) {
pendingSlideAnimations[viewHolder] = TweeningInfo(0f, viewHolder.itemView.height.toFloat())
dispatchAnimationStarted(viewHolder) dispatchAnimationStarted(viewHolder)
return true return true
} }
@ -49,7 +66,7 @@ class MultiselectItemAnimator(
} }
private fun animateSlide(viewHolder: RecyclerView.ViewHolder, preLayoutInfo: ItemHolderInfo?, postLayoutInfo: ItemHolderInfo, operation: Operation): Boolean { private fun animateSlide(viewHolder: RecyclerView.ViewHolder, preLayoutInfo: ItemHolderInfo?, postLayoutInfo: ItemHolderInfo, operation: Operation): Boolean {
if (isInMultiSelectMode() || isLoadingInitialContent()) { if (isInMultiSelectMode() || !shouldPlayMessageAnimations()) {
dispatchAnimationFinished(viewHolder) dispatchAnimationFinished(viewHolder)
return false return false
} }
@ -77,14 +94,14 @@ class MultiselectItemAnimator(
viewHolder.itemView.translationY = translationY viewHolder.itemView.translationY = translationY
pendingSlideAnimations.add(viewHolder) pendingSlideAnimations[viewHolder] = TweeningInfo(translationY, 0f)
dispatchAnimationStarted(viewHolder) dispatchAnimationStarted(viewHolder)
return true return true
} }
override fun animatePersistence(viewHolder: RecyclerView.ViewHolder, preLayoutInfo: ItemHolderInfo, postLayoutInfo: ItemHolderInfo): Boolean { override fun animatePersistence(viewHolder: RecyclerView.ViewHolder, preLayoutInfo: ItemHolderInfo, postLayoutInfo: ItemHolderInfo): Boolean {
val isInMultiSelectMode = isInMultiSelectMode() val isInMultiSelectMode = isInMultiSelectMode()
return if (!isInMultiSelectMode) { return if (!isInMultiSelectMode && shouldPlayMessageAnimations()) {
if (pendingSlideAnimations.contains(viewHolder) || slideAnimations.containsKey(viewHolder)) { if (pendingSlideAnimations.contains(viewHolder) || slideAnimations.containsKey(viewHolder)) {
dispatchAnimationFinished(viewHolder) dispatchAnimationFinished(viewHolder)
false false
@ -107,52 +124,42 @@ class MultiselectItemAnimator(
override fun runPendingAnimations() { override fun runPendingAnimations() {
runPendingSlideAnimations() runPendingSlideAnimations()
runPendingSlideOutAnimation()
} }
private fun runPendingSlideAnimations() { private fun runPendingSlideAnimations() {
for (viewHolder in pendingSlideAnimations) { val animators: MutableList<Animator> = mutableListOf()
val animator = ObjectAnimator.ofFloat(viewHolder.itemView, "translationY", 0f) for ((viewHolder, tweeningInfo) in pendingSlideAnimations) {
slideAnimations[viewHolder]?.cancel() val animator = ValueAnimator.ofFloat(0f, 1f)
slideAnimations[viewHolder] = animator slideAnimations[viewHolder] = AnimationInfo(animator, tweeningInfo)
animator.duration = 150L animator.duration = 150L
animator.addUpdateListener { animator.addUpdateListener {
if (viewHolder in slideAnimations) {
viewHolder.itemView.translationY = tweeningInfo.lerp(it.animatedFraction)
(viewHolder.itemView.parent as RecyclerView?)?.invalidate() (viewHolder.itemView.parent as RecyclerView?)?.invalidate()
} }
animator.doOnEnd {
viewHolder.itemView.translationY = 0f
slideAnimations.remove(viewHolder)
dispatchAnimationFinished(viewHolder)
dispatchFinishedWhenDone()
} }
animator.start() animator.doOnEnd {
if (viewHolder in slideAnimations) {
handleAnimationEnd(viewHolder)
}
}
animators.add(animator)
}
AnimatorSet().apply {
playTogether(animators)
start()
} }
pendingSlideAnimations.clear() pendingSlideAnimations.clear()
} }
private fun runPendingSlideOutAnimation() { private fun handleAnimationEnd(viewHolder: RecyclerView.ViewHolder) {
val viewHolder = pendingTypingViewSlideOut
if (viewHolder != null) {
pendingTypingViewSlideOut = null
slideAnimations[viewHolder]?.cancel()
val animator = ObjectAnimator.ofFloat(viewHolder.itemView, "translationY", viewHolder.itemView.height.toFloat())
slideAnimations[viewHolder] = animator
animator.duration = 150L
animator.addUpdateListener {
(viewHolder.itemView.parent as RecyclerView?)?.invalidate()
}
animator.doOnEnd {
viewHolder.itemView.translationY = 0f viewHolder.itemView.translationY = 0f
slideAnimations.remove(viewHolder) slideAnimations.remove(viewHolder)
dispatchAnimationFinished(viewHolder) dispatchAnimationFinished(viewHolder)
dispatchFinishedWhenDone() dispatchFinishedWhenDone()
} }
animator.start()
}
}
override fun endAnimation(item: RecyclerView.ViewHolder) { override fun endAnimation(item: RecyclerView.ViewHolder) {
endSlideAnimation(item) endSlideAnimation(item)
@ -164,7 +171,7 @@ class MultiselectItemAnimator(
} }
override fun isRunning(): Boolean { override fun isRunning(): Boolean {
return slideAnimations.values.any { it.isRunning } return slideAnimations.values.any { it.sharedAnimator.isRunning }
} }
override fun onAnimationFinished(viewHolder: RecyclerView.ViewHolder) { override fun onAnimationFinished(viewHolder: RecyclerView.ViewHolder) {
@ -173,11 +180,13 @@ class MultiselectItemAnimator(
} }
private fun endSlideAnimation(item: RecyclerView.ViewHolder) { private fun endSlideAnimation(item: RecyclerView.ViewHolder) {
slideAnimations[item]?.cancel() slideAnimations[item]?.sharedAnimator?.cancel()
} }
fun endSlideAnimations() { fun endSlideAnimations() {
slideAnimations.values.forEach { it.cancel() } slideAnimations.values.map { it.sharedAnimator }.forEach {
it.cancel()
}
} }
private fun dispatchFinishedWhenDone() { private fun dispatchFinishedWhenDone() {

Wyświetl plik

@ -12,7 +12,7 @@
android:layout_height="0dp" android:layout_height="0dp"
android:background="@color/reactions_screen_shade_color" android:background="@color/reactions_screen_shade_color"
app:layout_constraintTop_toBottomOf="@android:id/list" app:layout_constraintTop_toBottomOf="@android:id/list"
android:visibility="gone" /> android:visibility="invisible" />
<FrameLayout <FrameLayout
android:id="@+id/video_container" android:id="@+id/video_container"