mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-22 16:07:30 +00:00
SES-1145 - New messages are hidden under keyboard - MK3 (#1415)
* WIP * Working - push before cleanup * Fixes #1316 * Cleanup * PR review adjustments * Fixed scrolling when receiving an image based message while keyboard is up * Prevent auto-scroll to last seen item pos in conversation view if <= 3 * Put back <=3 check to scroll --------- Co-authored-by: = <=> Co-authored-by: AL-Session <160798022+AL-Session@users.noreply.github.com>
This commit is contained in:
parent
8c2aaa06d8
commit
1f249a6d5a
@ -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" >
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.thoughtcrime.securesms.home.HomeActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity"
|
||||
android:screenOrientation="portrait"
|
||||
|
@ -175,6 +175,7 @@ import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask
|
||||
import org.thoughtcrime.securesms.util.SimpleTextWatcher
|
||||
import org.thoughtcrime.securesms.util.isScrolledToBottom
|
||||
import org.thoughtcrime.securesms.util.isScrolledToWithin30dpOfBottom
|
||||
import org.thoughtcrime.securesms.util.push
|
||||
import org.thoughtcrime.securesms.util.show
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
@ -281,6 +282,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
private val isScrolledToBottom: Boolean
|
||||
get() = binding?.conversationRecyclerView?.isScrolledToBottom ?: true
|
||||
|
||||
private val isScrolledToWithin30dpOfBottom: Boolean
|
||||
get() = binding?.conversationRecyclerView?.isScrolledToWithin30dpOfBottom ?: true
|
||||
|
||||
private val layoutManager: LinearLayoutManager?
|
||||
get() { return binding?.conversationRecyclerView?.layoutManager as LinearLayoutManager? }
|
||||
|
||||
@ -336,6 +340,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
lifecycleCoroutineScope = lifecycleScope
|
||||
)
|
||||
adapter.visibleMessageViewDelegate = this
|
||||
|
||||
// Register an AdapterDataObserver to scroll us to the bottom of the RecyclerView if we're
|
||||
// already near the the bottom and the data changes.
|
||||
adapter.registerAdapterDataObserver(ConversationAdapterDataObserver(binding?.conversationRecyclerView!!, adapter))
|
||||
|
||||
adapter
|
||||
}
|
||||
|
||||
@ -352,6 +361,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
private lateinit var reactionDelegate: ConversationReactionDelegate
|
||||
private val reactWithAnyEmojiStartPage = -1
|
||||
|
||||
// Properties for what message indices are visible previously & now, as well as the scroll state
|
||||
private var previousLastVisibleRecyclerViewIndex: Int = RecyclerView.NO_POSITION
|
||||
private var currentLastVisibleRecyclerViewIndex: Int = RecyclerView.NO_POSITION
|
||||
private var recyclerScrollState: Int = RecyclerView.SCROLL_STATE_IDLE
|
||||
|
||||
// region Settings
|
||||
companion object {
|
||||
// Extras
|
||||
@ -375,6 +389,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
super.onCreate(savedInstanceState, isReady)
|
||||
binding = ActivityConversationV2Binding.inflate(layoutInflater)
|
||||
setContentView(binding!!.root)
|
||||
|
||||
// messageIdToScroll
|
||||
messageToScrollTimestamp.set(intent.getLongExtra(SCROLL_MESSAGE_ID, -1))
|
||||
messageToScrollAuthor.set(intent.getParcelableExtra(SCROLL_MESSAGE_AUTHOR))
|
||||
@ -390,6 +405,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
setUpLinkPreviewObserver()
|
||||
restoreDraftIfNeeded()
|
||||
setUpUiStateObserver()
|
||||
|
||||
binding!!.scrollToBottomButton.setOnClickListener {
|
||||
val layoutManager = (binding?.conversationRecyclerView?.layoutManager as? LinearLayoutManager) ?: return@setOnClickListener
|
||||
val targetPosition = if (reverseMessageList) 0 else adapter.itemCount
|
||||
@ -419,9 +435,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
setUpBlockedBanner()
|
||||
binding!!.searchBottomBar.setEventListener(this)
|
||||
updateSendAfterApprovalText()
|
||||
showOrHideInputIfNeeded()
|
||||
setUpMessageRequestsBar()
|
||||
|
||||
// Note: Do not `showOrHideInputIfNeeded` here - we'll never start this activity w/ the
|
||||
// keyboard visible and have no need to immediately display it.
|
||||
|
||||
val weakActivity = WeakReference(this)
|
||||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
@ -563,17 +581,45 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
binding!!.conversationRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
if (recyclerScrollState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
scrollToMostRecentMessageIfWeShould()
|
||||
}
|
||||
handleRecyclerViewScrolled()
|
||||
}
|
||||
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
|
||||
recyclerScrollState = newState
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
binding!!.conversationRecyclerView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
12
app/src/main/res/drawable/cross.xml
Normal file
12
app/src/main/res/drawable/cross.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="100"
|
||||
android:viewportHeight="100">
|
||||
|
||||
<path
|
||||
android:pathData="M0,0 L100,100 M0,100 L100,0"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="@android:color/white" />
|
||||
</vector>
|
@ -23,6 +23,10 @@
|
||||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<!--
|
||||
Add this to the below recycler view if you need to debug activity `adjustResize` issues:
|
||||
android:background="@drawable/cross"
|
||||
-->
|
||||
<org.thoughtcrime.securesms.conversation.v2.ConversationRecyclerView
|
||||
android:focusable="false"
|
||||
android:id="@+id/conversationRecyclerView"
|
||||
@ -31,6 +35,7 @@
|
||||
android:layout_above="@+id/typingIndicatorViewContainer"
|
||||
android:layout_below="@id/toolbar" />
|
||||
|
||||
|
||||
<org.thoughtcrime.securesms.conversation.v2.components.TypingIndicatorViewContainer
|
||||
android:focusable="false"
|
||||
android:id="@+id/typingIndicatorViewContainer"
|
||||
|
Loading…
x
Reference in New Issue
Block a user