diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/BodyBubbleLayoutTransition.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/BodyBubbleLayoutTransition.kt index d5a264550..b65a48b7e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/BodyBubbleLayoutTransition.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/BodyBubbleLayoutTransition.kt @@ -20,7 +20,7 @@ class BodyBubbleLayoutTransition(bodyBubble: ConversationItemBodyBubble) : Layou animator.duration = getAnimator(CHANGE_DISAPPEARING).duration animator.addUpdateListener { - val parentRecycler: RecyclerView? = bodyBubble.parent.parent as? RecyclerView + val parentRecycler: RecyclerView? = bodyBubble.parent?.parent as? RecyclerView try { parentRecycler?.invalidate() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java index dfcaf8aaa..054b7a7dd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java @@ -166,7 +166,7 @@ public class ConversationAdapter @Override public int getItemViewType(int position) { - if (hasHeader() && position == 0) { + if (isTypingViewEnabled() && position == 0) { return MESSAGE_TYPE_HEADER; } @@ -365,7 +365,7 @@ public class ConversationAdapter } public @Nullable ConversationMessage getItem(int position) { - position = hasHeader() ? position - 1 : position; + position = isTypingViewEnabled() ? position - 1 : position; if (position == -1) { return null; @@ -421,7 +421,7 @@ public class ConversationAdapter */ @MainThread 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; } @@ -611,7 +611,7 @@ public class ConversationAdapter } private boolean isHeaderPosition(int position) { - return hasHeader() && position == 0; + return isTypingViewEnabled() && position == 0; } private boolean isFooterPosition(int position) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationData.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationData.java index b3a5283f6..b876da918 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationData.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationData.java @@ -5,7 +5,7 @@ import androidx.annotation.NonNull; /** * Represents metadata about a conversation. */ -final class ConversationData { +public final class ConversationData { private final long threadId; private final long lastSeen; private final int lastSeenPosition; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 211cef058..cee4c84f0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -53,9 +53,7 @@ import androidx.core.app.ActivityOptionsCompat; import androidx.core.text.HtmlCompat; import androidx.core.view.ViewCompat; import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.Observer; -import androidx.lifecycle.Transformations; import androidx.lifecycle.ViewModelProviders; import androidx.recyclerview.widget.LinearLayoutManager; 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.colors.Colorizer; 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.MultiselectPart; 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.WindowUtil; 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.views.AdaptiveActionsToolbar; import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; @@ -223,8 +220,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect private ConversationUpdateTick conversationUpdateTick; private MultiselectItemDecoration multiselectItemDecoration; - private boolean initialDataLoaded = false; - public static void prepare(@NonNull Context context) { FrameLayout parent = new FrameLayout(context); parent.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)); @@ -258,15 +253,15 @@ public class ConversationFragment extends LoggingFragment implements Multiselect toolbarShadow = requireActivity().findViewById(R.id.conversation_toolbar_shadow); reactionsShade = view.findViewById(R.id.reactions_shade); - final LinearLayoutManager layoutManager = new SmoothScrollingLinearLayoutManager(getActivity(), true); - final MultiselectItemAnimator multiselectItemAnimator = new MultiselectItemAnimator(() -> { + final LinearLayoutManager layoutManager = new SmoothScrollingLinearLayoutManager(getActivity(), true); + final ConversationItemAnimator conversationItemAnimator = new ConversationItemAnimator(() -> { ConversationAdapter adapter = getListAdapter(); if (adapter == null) { return false; } else { return Util.hasItems(adapter.getSelectedItems()); } - }, () -> !initialDataLoaded, () -> list.canScrollVertically(1) || list.canScrollVertically(-1)); + }, () -> conversationViewModel.shouldPlayMessageAnimations(), () -> list.canScrollVertically(1) || list.canScrollVertically(-1)); multiselectItemDecoration = new MultiselectItemDecoration(requireContext(), () -> conversationViewModel.getWallpaper().getValue()); @@ -276,7 +271,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect RecyclerViewColorizer recyclerViewColorizer = new RecyclerViewColorizer(list); list.addItemDecoration(multiselectItemDecoration); - list.setItemAnimator(multiselectItemAnimator); + list.setItemAnimator(conversationItemAnimator); getViewLifecycleOwner().getLifecycle().addObserver(multiselectItemDecoration); @@ -307,25 +302,12 @@ public class ConversationFragment extends LoggingFragment implements Multiselect this.messageCountsViewModel = ViewModelProviders.of(requireActivity()).get(MessageCountsViewModel.class); this.conversationViewModel = ViewModelProviders.of(requireActivity(), new ConversationViewModel.Factory()).get(ConversationViewModel.class); - MutableLiveData hasSubmittedNonEmptyList = new MutableLiveData<>(false); - LiveData hasMessagesInThread = Transformations.map(conversationViewModel.getConversationMetadata(), c -> c.getThreadSize() > 0); - - LiveData 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.getMessages().observe(getViewLifecycleOwner(), messages -> { ConversationAdapter adapter = getListAdapter(); if (adapter != null) { 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() { multiselectItemDecoration.setFocusedItem(null); list.invalidateItemDecorations(); - reactionsShade.setVisibility(View.GONE); + reactionsShade.setVisibility(View.INVISIBLE); } private void updateConversationItemTimestamps() { @@ -1040,7 +1022,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect .submit(); } else if (conversation.getMessageRequestData().isMessageRequestAccepted()) { 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) .submit(); } else { @@ -1064,7 +1046,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect } private boolean isTypingIndicatorShowing() { - return getListAdapter().hasHeader(); + return getListAdapter().isTypingViewEnabled(); } public void onSearchQueryUpdated(@Nullable String query) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java index 2a184d949..30b6ac31b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java @@ -6,6 +6,7 @@ import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Observer; import androidx.lifecycle.Transformations; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; @@ -37,8 +38,10 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.DefaultValueLiveData; import org.thoughtcrime.securesms.util.SingleLiveEvent; +import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; +import org.thoughtcrime.securesms.util.livedata.Store; import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.guava.Optional; @@ -76,6 +79,8 @@ public class ConversationViewModel extends ViewModel { private final MutableLiveData toolbarBottom; private final MutableLiveData inlinePlayerHeight; private final LiveData conversationTopMargin; + private final Store threadAnimationStateStore; + private final Observer threadAnimationStateStoreDriver; private final Map> sessionMemberCache = new HashMap<>(); @@ -83,22 +88,23 @@ public class ConversationViewModel extends ViewModel { private int jumpToPosition; private ConversationViewModel() { - this.context = ApplicationDependencies.getApplication(); - this.mediaRepository = new MediaRepository(); - this.conversationRepository = new ConversationRepository(); - this.recentMedia = new MutableLiveData<>(); - this.threadId = new MutableLiveData<>(); - this.showScrollButtons = new MutableLiveData<>(false); - this.hasUnreadMentions = new MutableLiveData<>(false); - this.recipientId = new MutableLiveData<>(); - this.events = new SingleLiveEvent<>(); - this.pagingController = new ProxyPagingController<>(); - this.conversationObserver = pagingController::onDataInvalidated; - this.messageUpdateObserver = pagingController::onDataItemChanged; - this.messageInsertObserver = messageId -> pagingController.onDataItemInserted(messageId, 0); - this.toolbarBottom = new MutableLiveData<>(); - this.inlinePlayerHeight = new MutableLiveData<>(); - this.conversationTopMargin = Transformations.distinctUntilChanged(LiveDataUtil.combineLatest(toolbarBottom, inlinePlayerHeight, Integer::sum)); + this.context = ApplicationDependencies.getApplication(); + this.mediaRepository = new MediaRepository(); + this.conversationRepository = new ConversationRepository(); + this.recentMedia = new MutableLiveData<>(); + this.threadId = new MutableLiveData<>(); + this.showScrollButtons = new MutableLiveData<>(false); + this.hasUnreadMentions = new MutableLiveData<>(false); + this.recipientId = new MutableLiveData<>(); + this.events = new SingleLiveEvent<>(); + this.pagingController = new ProxyPagingController<>(); + this.conversationObserver = pagingController::onDataInvalidated; + this.messageUpdateObserver = pagingController::onDataItemChanged; + this.messageInsertObserver = messageId -> pagingController.onDataItemInserted(messageId, 0); + this.toolbarBottom = new MutableLiveData<>(); + this.inlinePlayerHeight = new MutableLiveData<>(); + this.conversationTopMargin = Transformations.distinctUntilChanged(LiveDataUtil.combineLatest(toolbarBottom, inlinePlayerHeight, Integer::sum)); + this.threadAnimationStateStore = new Store<>(new ThreadAnimationState(-1L, null, false)); LiveData recipientLiveData = LiveDataUtil.mapAsync(recipientId, Recipient::resolved); LiveData threadAndRecipient = LiveDataUtil.combineLatest(threadId, recipientLiveData, ThreadAndRecipient::new); @@ -158,6 +164,41 @@ public class ConversationViewModel extends ViewModel { chatColors = LiveDataUtil.mapDistinct(Transformations.switchMap(recipientId, id -> Recipient.live(id).getLiveData()), 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 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) { @@ -301,6 +342,7 @@ public class ConversationViewModel extends ViewModel { @Override protected void onCleared() { super.onCleared(); + threadAnimationStateStore.getStateLiveData().removeObserver(threadAnimationStateStoreDriver); ApplicationDependencies.getDatabaseObserver().unregisterObserver(conversationObserver); ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageUpdateObserver); ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageInsertObserver); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ThreadAnimationState.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ThreadAnimationState.kt new file mode 100644 index 000000000..207657b85 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ThreadAnimationState.kt @@ -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 + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemAnimator.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/ConversationItemAnimator.kt similarity index 64% rename from app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemAnimator.kt rename to app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/ConversationItemAnimator.kt index e1190f413..5b62f0bea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemAnimator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/ConversationItemAnimator.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.conversation.mutiselect -import android.animation.ObjectAnimator +import android.animation.Animator +import android.animation.AnimatorSet import android.animation.ValueAnimator import androidx.core.animation.doOnEnd 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. */ -class MultiselectItemAnimator( +class ConversationItemAnimator( private val isInMultiSelectMode: () -> Boolean, - private val isLoadingInitialContent: () -> Boolean, + private val shouldPlayMessageAnimations: () -> Boolean, private val isParentFilled: () -> Boolean ) : RecyclerView.ItemAnimator() { @@ -23,14 +24,30 @@ class MultiselectItemAnimator( CHANGE } - private val pendingSlideAnimations: MutableSet = mutableSetOf() - private var pendingTypingViewSlideOut: RecyclerView.ViewHolder? = null + private data class TweeningInfo( + val startValue: Float, + val endValue: Float + ) { + fun lerp(progress: Float): Float { + return startValue + progress * (endValue - startValue) + } + } - private val slideAnimations: MutableMap = mutableMapOf() + private data class AnimationInfo( + val sharedAnimator: ValueAnimator, + val tweeningInfo: TweeningInfo + ) + + private val pendingSlideAnimations: MutableMap = mutableMapOf() + private val slideAnimations: MutableMap = mutableMapOf() override fun animateDisappearance(viewHolder: RecyclerView.ViewHolder, preLayoutInfo: ItemHolderInfo, postLayoutInfo: ItemHolderInfo?): Boolean { - if (viewHolder is ConversationAdapter.HeaderViewHolder && pendingTypingViewSlideOut == null) { - pendingTypingViewSlideOut = viewHolder + if (viewHolder is ConversationAdapter.HeaderViewHolder && + !pendingSlideAnimations.containsKey(viewHolder) && + !slideAnimations.containsKey(viewHolder) && + shouldPlayMessageAnimations() + ) { + pendingSlideAnimations[viewHolder] = TweeningInfo(0f, viewHolder.itemView.height.toFloat()) dispatchAnimationStarted(viewHolder) return true } @@ -49,7 +66,7 @@ class MultiselectItemAnimator( } private fun animateSlide(viewHolder: RecyclerView.ViewHolder, preLayoutInfo: ItemHolderInfo?, postLayoutInfo: ItemHolderInfo, operation: Operation): Boolean { - if (isInMultiSelectMode() || isLoadingInitialContent()) { + if (isInMultiSelectMode() || !shouldPlayMessageAnimations()) { dispatchAnimationFinished(viewHolder) return false } @@ -77,14 +94,14 @@ class MultiselectItemAnimator( viewHolder.itemView.translationY = translationY - pendingSlideAnimations.add(viewHolder) + pendingSlideAnimations[viewHolder] = TweeningInfo(translationY, 0f) dispatchAnimationStarted(viewHolder) return true } override fun animatePersistence(viewHolder: RecyclerView.ViewHolder, preLayoutInfo: ItemHolderInfo, postLayoutInfo: ItemHolderInfo): Boolean { val isInMultiSelectMode = isInMultiSelectMode() - return if (!isInMultiSelectMode) { + return if (!isInMultiSelectMode && shouldPlayMessageAnimations()) { if (pendingSlideAnimations.contains(viewHolder) || slideAnimations.containsKey(viewHolder)) { dispatchAnimationFinished(viewHolder) false @@ -107,51 +124,41 @@ class MultiselectItemAnimator( override fun runPendingAnimations() { runPendingSlideAnimations() - runPendingSlideOutAnimation() } private fun runPendingSlideAnimations() { - for (viewHolder in pendingSlideAnimations) { - val animator = ObjectAnimator.ofFloat(viewHolder.itemView, "translationY", 0f) - slideAnimations[viewHolder]?.cancel() - slideAnimations[viewHolder] = animator + val animators: MutableList = mutableListOf() + for ((viewHolder, tweeningInfo) in pendingSlideAnimations) { + val animator = ValueAnimator.ofFloat(0f, 1f) + slideAnimations[viewHolder] = AnimationInfo(animator, tweeningInfo) animator.duration = 150L animator.addUpdateListener { - (viewHolder.itemView.parent as RecyclerView?)?.invalidate() + if (viewHolder in slideAnimations) { + viewHolder.itemView.translationY = tweeningInfo.lerp(it.animatedFraction) + (viewHolder.itemView.parent as RecyclerView?)?.invalidate() + } } animator.doOnEnd { - viewHolder.itemView.translationY = 0f - slideAnimations.remove(viewHolder) - dispatchAnimationFinished(viewHolder) - dispatchFinishedWhenDone() + if (viewHolder in slideAnimations) { + handleAnimationEnd(viewHolder) + } } - animator.start() + animators.add(animator) + } + + AnimatorSet().apply { + playTogether(animators) + start() } pendingSlideAnimations.clear() } - private fun runPendingSlideOutAnimation() { - 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 - slideAnimations.remove(viewHolder) - dispatchAnimationFinished(viewHolder) - dispatchFinishedWhenDone() - } - animator.start() - } + private fun handleAnimationEnd(viewHolder: RecyclerView.ViewHolder) { + viewHolder.itemView.translationY = 0f + slideAnimations.remove(viewHolder) + dispatchAnimationFinished(viewHolder) + dispatchFinishedWhenDone() } override fun endAnimation(item: RecyclerView.ViewHolder) { @@ -164,7 +171,7 @@ class MultiselectItemAnimator( } override fun isRunning(): Boolean { - return slideAnimations.values.any { it.isRunning } + return slideAnimations.values.any { it.sharedAnimator.isRunning } } override fun onAnimationFinished(viewHolder: RecyclerView.ViewHolder) { @@ -173,11 +180,13 @@ class MultiselectItemAnimator( } private fun endSlideAnimation(item: RecyclerView.ViewHolder) { - slideAnimations[item]?.cancel() + slideAnimations[item]?.sharedAnimator?.cancel() } fun endSlideAnimations() { - slideAnimations.values.forEach { it.cancel() } + slideAnimations.values.map { it.sharedAnimator }.forEach { + it.cancel() + } } private fun dispatchFinishedWhenDone() { diff --git a/app/src/main/res/layout/conversation_fragment.xml b/app/src/main/res/layout/conversation_fragment.xml index ceea46422..fa0098cf4 100644 --- a/app/src/main/res/layout/conversation_fragment.xml +++ b/app/src/main/res/layout/conversation_fragment.xml @@ -12,7 +12,7 @@ android:layout_height="0dp" android:background="@color/reactions_screen_shade_color" app:layout_constraintTop_toBottomOf="@android:id/list" - android:visibility="gone" /> + android:visibility="invisible" />