mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-23 00:17:34 +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:name="org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:parentActivityName="org.thoughtcrime.securesms.home.HomeActivity"
|
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
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="org.thoughtcrime.securesms.home.HomeActivity" />
|
android:value="org.thoughtcrime.securesms.home.HomeActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity"
|
android:name="org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
|
@ -175,6 +175,7 @@ import org.thoughtcrime.securesms.util.MediaUtil
|
|||||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask
|
||||||
import org.thoughtcrime.securesms.util.SimpleTextWatcher
|
import org.thoughtcrime.securesms.util.SimpleTextWatcher
|
||||||
import org.thoughtcrime.securesms.util.isScrolledToBottom
|
import org.thoughtcrime.securesms.util.isScrolledToBottom
|
||||||
|
import org.thoughtcrime.securesms.util.isScrolledToWithin30dpOfBottom
|
||||||
import org.thoughtcrime.securesms.util.push
|
import org.thoughtcrime.securesms.util.push
|
||||||
import org.thoughtcrime.securesms.util.show
|
import org.thoughtcrime.securesms.util.show
|
||||||
import org.thoughtcrime.securesms.util.toPx
|
import org.thoughtcrime.securesms.util.toPx
|
||||||
@ -281,6 +282,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
private val isScrolledToBottom: Boolean
|
private val isScrolledToBottom: Boolean
|
||||||
get() = binding?.conversationRecyclerView?.isScrolledToBottom ?: true
|
get() = binding?.conversationRecyclerView?.isScrolledToBottom ?: true
|
||||||
|
|
||||||
|
private val isScrolledToWithin30dpOfBottom: Boolean
|
||||||
|
get() = binding?.conversationRecyclerView?.isScrolledToWithin30dpOfBottom ?: true
|
||||||
|
|
||||||
private val layoutManager: LinearLayoutManager?
|
private val layoutManager: LinearLayoutManager?
|
||||||
get() { return binding?.conversationRecyclerView?.layoutManager as LinearLayoutManager? }
|
get() { return binding?.conversationRecyclerView?.layoutManager as LinearLayoutManager? }
|
||||||
|
|
||||||
@ -336,6 +340,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
lifecycleCoroutineScope = lifecycleScope
|
lifecycleCoroutineScope = lifecycleScope
|
||||||
)
|
)
|
||||||
adapter.visibleMessageViewDelegate = this
|
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
|
adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,6 +361,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
private lateinit var reactionDelegate: ConversationReactionDelegate
|
private lateinit var reactionDelegate: ConversationReactionDelegate
|
||||||
private val reactWithAnyEmojiStartPage = -1
|
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
|
// region Settings
|
||||||
companion object {
|
companion object {
|
||||||
// Extras
|
// Extras
|
||||||
@ -375,6 +389,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
super.onCreate(savedInstanceState, isReady)
|
super.onCreate(savedInstanceState, isReady)
|
||||||
binding = ActivityConversationV2Binding.inflate(layoutInflater)
|
binding = ActivityConversationV2Binding.inflate(layoutInflater)
|
||||||
setContentView(binding!!.root)
|
setContentView(binding!!.root)
|
||||||
|
|
||||||
// messageIdToScroll
|
// messageIdToScroll
|
||||||
messageToScrollTimestamp.set(intent.getLongExtra(SCROLL_MESSAGE_ID, -1))
|
messageToScrollTimestamp.set(intent.getLongExtra(SCROLL_MESSAGE_ID, -1))
|
||||||
messageToScrollAuthor.set(intent.getParcelableExtra(SCROLL_MESSAGE_AUTHOR))
|
messageToScrollAuthor.set(intent.getParcelableExtra(SCROLL_MESSAGE_AUTHOR))
|
||||||
@ -390,6 +405,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
setUpLinkPreviewObserver()
|
setUpLinkPreviewObserver()
|
||||||
restoreDraftIfNeeded()
|
restoreDraftIfNeeded()
|
||||||
setUpUiStateObserver()
|
setUpUiStateObserver()
|
||||||
|
|
||||||
binding!!.scrollToBottomButton.setOnClickListener {
|
binding!!.scrollToBottomButton.setOnClickListener {
|
||||||
val layoutManager = (binding?.conversationRecyclerView?.layoutManager as? LinearLayoutManager) ?: return@setOnClickListener
|
val layoutManager = (binding?.conversationRecyclerView?.layoutManager as? LinearLayoutManager) ?: return@setOnClickListener
|
||||||
val targetPosition = if (reverseMessageList) 0 else adapter.itemCount
|
val targetPosition = if (reverseMessageList) 0 else adapter.itemCount
|
||||||
@ -419,9 +435,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
setUpBlockedBanner()
|
setUpBlockedBanner()
|
||||||
binding!!.searchBottomBar.setEventListener(this)
|
binding!!.searchBottomBar.setEventListener(this)
|
||||||
updateSendAfterApprovalText()
|
updateSendAfterApprovalText()
|
||||||
showOrHideInputIfNeeded()
|
|
||||||
setUpMessageRequestsBar()
|
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)
|
val weakActivity = WeakReference(this)
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
@ -563,17 +581,45 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
binding!!.conversationRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
binding!!.conversationRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
if (recyclerScrollState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||||
|
scrollToMostRecentMessageIfWeShould()
|
||||||
|
}
|
||||||
handleRecyclerViewScrolled()
|
handleRecyclerViewScrolled()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
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
|
// called from onCreate
|
||||||
@ -760,13 +806,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
// of the first unread message in the middle of the screen
|
// of the first unread message in the middle of the screen
|
||||||
if (isFirstLoad && !reverseMessageList) {
|
if (isFirstLoad && !reverseMessageList) {
|
||||||
layoutManager?.scrollToPositionWithOffset(lastSeenItemPosition, ((layoutManager?.height ?: 0) / 2))
|
layoutManager?.scrollToPositionWithOffset(lastSeenItemPosition, ((layoutManager?.height ?: 0) / 2))
|
||||||
|
|
||||||
if (shouldHighlight) { highlightViewAtPosition(lastSeenItemPosition) }
|
if (shouldHighlight) { highlightViewAtPosition(lastSeenItemPosition) }
|
||||||
|
|
||||||
return lastSeenItemPosition
|
return lastSeenItemPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastSeenItemPosition <= 3) { return lastSeenItemPosition }
|
if (lastSeenItemPosition <= 3) { return lastSeenItemPosition }
|
||||||
|
|
||||||
binding?.conversationRecyclerView?.scrollToPosition(lastSeenItemPosition)
|
binding?.conversationRecyclerView?.scrollToPosition(lastSeenItemPosition)
|
||||||
return lastSeenItemPosition
|
return lastSeenItemPosition
|
||||||
}
|
}
|
||||||
@ -1040,8 +1085,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
|
|
||||||
private fun handleRecyclerViewScrolled() {
|
private fun handleRecyclerViewScrolled() {
|
||||||
val binding = binding ?: return
|
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
|
val wasTypingIndicatorVisibleBefore = binding.typingIndicatorViewContainer.isVisible
|
||||||
binding.typingIndicatorViewContainer.isVisible = wasTypingIndicatorVisibleBefore && isScrolledToBottom
|
binding.typingIndicatorViewContainer.isVisible = wasTypingIndicatorVisibleBefore && isScrolledToBottom
|
||||||
|
|
||||||
showScrollToBottomButtonIfApplicable()
|
showScrollToBottomButtonIfApplicable()
|
||||||
val maybeTargetVisiblePosition = if (reverseMessageList) layoutManager?.findFirstVisibleItemPosition() else layoutManager?.findLastVisibleItemPosition()
|
val maybeTargetVisiblePosition = if (reverseMessageList) layoutManager?.findFirstVisibleItemPosition() else layoutManager?.findLastVisibleItemPosition()
|
||||||
val targetVisiblePosition = maybeTargetVisiblePosition ?: RecyclerView.NO_POSITION
|
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) +
|
get() = computeVerticalScrollOffset().coerceAtLeast(0) +
|
||||||
computeVerticalScrollExtent() +
|
computeVerticalScrollExtent() +
|
||||||
toPx(50, resources) >= computeVerticalScrollRange()
|
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>
|
</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
|
<org.thoughtcrime.securesms.conversation.v2.ConversationRecyclerView
|
||||||
android:focusable="false"
|
android:focusable="false"
|
||||||
android:id="@+id/conversationRecyclerView"
|
android:id="@+id/conversationRecyclerView"
|
||||||
@ -31,6 +35,7 @@
|
|||||||
android:layout_above="@+id/typingIndicatorViewContainer"
|
android:layout_above="@+id/typingIndicatorViewContainer"
|
||||||
android:layout_below="@id/toolbar" />
|
android:layout_below="@id/toolbar" />
|
||||||
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.components.TypingIndicatorViewContainer
|
<org.thoughtcrime.securesms.conversation.v2.components.TypingIndicatorViewContainer
|
||||||
android:focusable="false"
|
android:focusable="false"
|
||||||
android:id="@+id/typingIndicatorViewContainer"
|
android:id="@+id/typingIndicatorViewContainer"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user