diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c89fca8a70..d34a36f1b2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -230,11 +230,13 @@ android:name="org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2" android:screenOrientation="portrait" android:parentActivityName="org.thoughtcrime.securesms.home.HomeActivity" - android:theme="@style/Theme.Session.DayNight.NoActionBar"> + android:theme="@style/Theme.Session.DayNight.NoActionBar" + android:windowSoftInputMode="adjustResize" > + - showScrollToBottomButtonIfApplicable() + private fun scrollToMostRecentMessageIfWeShould() { + // Grab an initial 'previous' last visible message.. + if (previousLastVisibleRecyclerViewIndex == RecyclerView.NO_POSITION) { + previousLastVisibleRecyclerViewIndex = layoutManager?.findLastVisibleItemPosition()!! } + + // ..and grab the 'current' last visible message. + currentLastVisibleRecyclerViewIndex = layoutManager?.findLastVisibleItemPosition()!! + + // If the current last visible message index is less than the previous one (i.e. we've + // lost visibility of one or more messages due to showing the IME keyboard) AND we're + // at the bottom of the message feed.. + val atBottomAndTrueLastNoLongerVisible = currentLastVisibleRecyclerViewIndex!! <= previousLastVisibleRecyclerViewIndex!! && !binding?.scrollToBottomButton?.isVisible!! + + // ..OR we're at the last message or have received a new message.. + val atLastOrReceivedNewMessage = currentLastVisibleRecyclerViewIndex == (adapter.itemCount - 1) + + // ..then scroll the recycler view to the last message on resize. Note: We cannot just call + // scroll/smoothScroll - we have to `post` it or nothing happens! + if (atBottomAndTrueLastNoLongerVisible || atLastOrReceivedNewMessage) { + binding?.conversationRecyclerView?.post { + binding?.conversationRecyclerView?.smoothScrollToPosition(adapter.itemCount) + } + } + + // Update our previous last visible view index to the current one + previousLastVisibleRecyclerViewIndex = currentLastVisibleRecyclerViewIndex } // called from onCreate @@ -760,13 +806,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // of the first unread message in the middle of the screen if (isFirstLoad && !reverseMessageList) { layoutManager?.scrollToPositionWithOffset(lastSeenItemPosition, ((layoutManager?.height ?: 0) / 2)) - if (shouldHighlight) { highlightViewAtPosition(lastSeenItemPosition) } - return lastSeenItemPosition } if (lastSeenItemPosition <= 3) { return lastSeenItemPosition } + binding?.conversationRecyclerView?.scrollToPosition(lastSeenItemPosition) return lastSeenItemPosition } @@ -1040,8 +1085,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private fun handleRecyclerViewScrolled() { val binding = binding ?: return + + // Note: The typing indicate is whether the other person / other people are typing - it has + // nothing to do with the IME keyboard state. val wasTypingIndicatorVisibleBefore = binding.typingIndicatorViewContainer.isVisible binding.typingIndicatorViewContainer.isVisible = wasTypingIndicatorVisibleBefore && isScrolledToBottom + showScrollToBottomButtonIfApplicable() val maybeTargetVisiblePosition = if (reverseMessageList) layoutManager?.findFirstVisibleItemPosition() else layoutManager?.findLastVisibleItemPosition() val targetVisiblePosition = maybeTargetVisiblePosition ?: RecyclerView.NO_POSITION @@ -2107,4 +2156,15 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } } + // AdapterDataObserver implementation to scroll us to the bottom of the ConversationRecyclerView + // when we're already near the bottom and we send or receive a message. + inner class ConversationAdapterDataObserver(val recyclerView: ConversationRecyclerView, val adapter: ConversationAdapter) : RecyclerView.AdapterDataObserver() { + override fun onChanged() { + super.onChanged() + if (recyclerView.isScrolledToWithin30dpOfBottom) { + recyclerView.scrollToPosition(adapter.itemCount-1) + } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/GeneralUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/GeneralUtilities.kt index a38c93831e..9124765763 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/GeneralUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/GeneralUtilities.kt @@ -37,3 +37,8 @@ val RecyclerView.isScrolledToBottom: Boolean get() = computeVerticalScrollOffset().coerceAtLeast(0) + computeVerticalScrollExtent() + toPx(50, resources) >= computeVerticalScrollRange() + +val RecyclerView.isScrolledToWithin30dpOfBottom: Boolean + get() = computeVerticalScrollOffset().coerceAtLeast(0) + + computeVerticalScrollExtent() + + toPx(30, resources) >= computeVerticalScrollRange() \ No newline at end of file diff --git a/app/src/main/res/drawable/cross.xml b/app/src/main/res/drawable/cross.xml new file mode 100644 index 0000000000..5b090de2b3 --- /dev/null +++ b/app/src/main/res/drawable/cross.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_conversation_v2.xml b/app/src/main/res/layout/activity_conversation_v2.xml index 000b860841..6fe0c4db60 100644 --- a/app/src/main/res/layout/activity_conversation_v2.xml +++ b/app/src/main/res/layout/activity_conversation_v2.xml @@ -23,6 +23,10 @@ + +