Add scroll to bottom & unread chat message counter in chat room while scrolling up in history
This commit is contained in:
parent
76bf525244
commit
338c136778
9 changed files with 130 additions and 5 deletions
|
@ -22,6 +22,7 @@ Group changes to describe their impact on the project, as follows:
|
|||
|
||||
### Changed
|
||||
- UI has been reworked around SlidingPane component to better handle tablets & foldable devices
|
||||
- No longer scroll to bottom of chat room when new messages are received, a new button shows up to do it
|
||||
- Animations have been replaced to use com.google.android.material.transition ones
|
||||
- Using new [Unified Content API](https://developer.android.com/about/versions/12/features/unified-content-api) to share files from keyboard (or other sources)
|
||||
- Bumped dependencies, gradle updated from 4.2.2 to 7.0.2
|
||||
|
|
|
@ -29,6 +29,8 @@ internal abstract class ChatScrollListener(private val mLayoutManager: LinearLay
|
|||
// True if we are still waiting for the last set of data to load.
|
||||
private var loading = true
|
||||
|
||||
var userHasScrolledUp: Boolean = false
|
||||
|
||||
// This happens many times a second during a scroll, so be wary of the code you place here.
|
||||
// We are given a few useful parameters to help us work out if we need to load some more data,
|
||||
// but first we check if we are waiting for the previous load to finish.
|
||||
|
@ -54,6 +56,13 @@ internal abstract class ChatScrollListener(private val mLayoutManager: LinearLay
|
|||
previousTotalItemCount = totalItemCount
|
||||
}
|
||||
|
||||
userHasScrolledUp = lastVisibleItemPosition != totalItemCount - 1
|
||||
if (userHasScrolledUp) {
|
||||
onScrolledUp()
|
||||
} else {
|
||||
onScrolledToEnd()
|
||||
}
|
||||
|
||||
// If it isn’t currently loading, we check to see if we have breached
|
||||
// the mVisibleThreshold and need to reload more data.
|
||||
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
|
||||
|
@ -67,6 +76,12 @@ internal abstract class ChatScrollListener(private val mLayoutManager: LinearLay
|
|||
// Defines the process for actually loading more data based on page
|
||||
protected abstract fun onLoadMore(totalItemsCount: Int)
|
||||
|
||||
// Called when user has started to scroll up, opposed to onScrolledToEnd()
|
||||
protected abstract fun onScrolledUp()
|
||||
|
||||
// Called when user has scrolled and reached the end of the items
|
||||
protected abstract fun onScrolledToEnd()
|
||||
|
||||
companion object {
|
||||
// The minimum amount of items to have below your current scroll position
|
||||
// before loading more.
|
||||
|
|
|
@ -75,8 +75,15 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
adapter.notifyItemChanged(positionStart - 1) // For grouping purposes
|
||||
|
||||
// Scroll to newly added messages automatically
|
||||
if (positionStart == adapter.itemCount - itemCount) {
|
||||
scrollToBottom()
|
||||
// But only if user hasn't initiated a scroll up in the messages history
|
||||
if (viewModel.isUserScrollingUp.value == false) {
|
||||
scrollToBottom()
|
||||
viewModel.chatRoom.markAsRead()
|
||||
} else {
|
||||
Log.w("[Chat Room] User has scrolled up manually in the messages history, don't scroll to the newly added message at the bottom & don't mark the chat room as read")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -191,10 +198,20 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
RecyclerViewSwipeUtils(ItemTouchHelper.RIGHT, swipeConfiguration, swipeListener)
|
||||
.attachToRecyclerView(binding.chatMessagesList)*/
|
||||
|
||||
val chatScrollListener: ChatScrollListener = object : ChatScrollListener(layoutManager) {
|
||||
val chatScrollListener = object : ChatScrollListener(layoutManager) {
|
||||
override fun onLoadMore(totalItemsCount: Int) {
|
||||
listViewModel.loadMoreData(totalItemsCount)
|
||||
}
|
||||
|
||||
override fun onScrolledUp() {
|
||||
viewModel.isUserScrollingUp.value = true
|
||||
}
|
||||
|
||||
override fun onScrolledToEnd() {
|
||||
viewModel.isUserScrollingUp.value = false
|
||||
Log.i("[Chat Room] User has scrolled to the latest message, mark chat room as read")
|
||||
viewModel.chatRoom.markAsRead()
|
||||
}
|
||||
}
|
||||
binding.chatMessagesList.addOnScrollListener(chatScrollListener)
|
||||
|
||||
|
@ -440,6 +457,10 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
false
|
||||
}
|
||||
|
||||
binding.setScrollToBottomClickListener {
|
||||
smoothScrollToPosition()
|
||||
}
|
||||
|
||||
if (textToShare?.isNotEmpty() == true) {
|
||||
Log.i("[Chat Room] Found text to share")
|
||||
chatSendingViewModel.textToSend.value = textToShare
|
||||
|
@ -715,6 +736,12 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
}
|
||||
}
|
||||
|
||||
private fun smoothScrollToPosition() {
|
||||
if (_adapter != null && adapter.itemCount > 0) {
|
||||
binding.chatMessagesList.smoothScrollToPosition(adapter.itemCount - 1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun pickFile() {
|
||||
val intentsList = ArrayList<Intent>()
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import androidx.lifecycle.MutableLiveData
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
import org.linphone.activities.main.chat.data.EventLogData
|
||||
import org.linphone.core.*
|
||||
import org.linphone.core.tools.Log
|
||||
|
@ -212,7 +213,10 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
|
|||
|
||||
private fun getEvents(): ArrayList<EventLogData> {
|
||||
val list = arrayListOf<EventLogData>()
|
||||
val history = chatRoom.getHistoryEvents(MESSAGES_PER_PAGE)
|
||||
val unreadCount = chatRoom.unreadMessagesCount
|
||||
val loadCount = max(MESSAGES_PER_PAGE, unreadCount)
|
||||
Log.i("[Chat Messages] $unreadCount unread messages in this chat room, loading $loadCount from history")
|
||||
val history = chatRoom.getHistoryEvents(loadCount)
|
||||
for (eventLog in history) {
|
||||
list.add(EventLogData(eventLog))
|
||||
}
|
||||
|
|
|
@ -19,10 +19,13 @@
|
|||
*/
|
||||
package org.linphone.activities.main.chat.viewmodels
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.view.animation.LinearInterpolator
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.R
|
||||
import org.linphone.contact.Contact
|
||||
import org.linphone.contact.ContactDataInterface
|
||||
|
@ -81,12 +84,29 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
|||
MutableLiveData<Boolean>()
|
||||
}
|
||||
|
||||
val isUserScrollingUp = MutableLiveData<Boolean>()
|
||||
|
||||
var oneParticipantOneDevice: Boolean = false
|
||||
|
||||
var addressToCall: Address? = null
|
||||
|
||||
var onlyParticipantOnlyDeviceAddress: Address? = null
|
||||
|
||||
val chatUnreadCountTranslateY = MutableLiveData<Float>()
|
||||
|
||||
private val bounceAnimator: ValueAnimator by lazy {
|
||||
ValueAnimator.ofFloat(AppUtils.getDimension(R.dimen.tabs_fragment_unread_count_bounce_offset), 0f).apply {
|
||||
addUpdateListener {
|
||||
val value = it.animatedValue as Float
|
||||
chatUnreadCountTranslateY.value = value
|
||||
}
|
||||
interpolator = LinearInterpolator()
|
||||
duration = 250
|
||||
repeatMode = ValueAnimator.REVERSE
|
||||
repeatCount = ValueAnimator.INFINITE
|
||||
}
|
||||
}
|
||||
|
||||
private val contactsUpdatedListener = object : ContactsUpdatedListenerStub() {
|
||||
override fun onContactsUpdated() {
|
||||
Log.i("[Chat Room] Contacts have changed")
|
||||
|
@ -202,6 +222,8 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
|||
|
||||
callInProgress.value = chatRoom.core.callsNb > 0
|
||||
updateRemotesComposing()
|
||||
|
||||
if (corePreferences.enableAnimations) bounceAnimator.start()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
|
@ -213,6 +235,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
|||
coreContext.contactsManager.removeListener(contactsUpdatedListener)
|
||||
chatRoom.removeListener(chatRoomListener)
|
||||
chatRoom.core.removeListener(coreListener)
|
||||
if (corePreferences.enableAnimations) bounceAnimator.end()
|
||||
}
|
||||
|
||||
fun hideMenu(): Boolean {
|
||||
|
|
|
@ -142,8 +142,8 @@ class NotificationsManager(private val context: Context) {
|
|||
}
|
||||
|
||||
if (currentlyDisplayedChatRoomAddress == room.peerAddress.asStringUriOnly()) {
|
||||
Log.i("[Notifications Manager] Chat room is currently displayed, do not notify received message & mark it as read")
|
||||
room.markAsRead()
|
||||
Log.i("[Notifications Manager] Chat room is currently displayed, do not notify received message")
|
||||
// Mark as read is now done in the DetailChatRoomFragment
|
||||
return
|
||||
}
|
||||
|
||||
|
|
BIN
app/src/main/res/drawable-xhdpi/scroll_to_bottom_default.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/scroll_to_bottom_default.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
20
app/src/main/res/drawable/scroll_to_bottom.xml
Normal file
20
app/src/main/res/drawable/scroll_to_bottom.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<bitmap android:src="@drawable/scroll_to_bottom_default"
|
||||
android:tint="?attr/drawableTintOverColor"/>
|
||||
</item>
|
||||
<item android:state_enabled="false">
|
||||
<bitmap android:src="@drawable/scroll_to_bottom_default"
|
||||
android:tint="?attr/drawableTintDisabledColor"/>
|
||||
</item>
|
||||
<item android:state_selected="true">
|
||||
<bitmap android:src="@drawable/scroll_to_bottom_default"
|
||||
android:tint="?attr/drawableTintOverColor"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:src="@drawable/scroll_to_bottom_default"
|
||||
android:tint="?attr/drawableTintColor"/>
|
||||
</item>
|
||||
</selector>
|
||||
|
|
@ -26,6 +26,9 @@
|
|||
<variable
|
||||
name="voiceRecordingTouchListener"
|
||||
type="android.view.View.OnTouchListener" />
|
||||
<variable
|
||||
name="scrollToBottomClickListener"
|
||||
type="android.view.View.OnClickListener"/>
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.activities.main.chat.viewmodels.ChatRoomViewModel" />
|
||||
|
@ -297,6 +300,38 @@
|
|||
android:layout_marginTop="8dp"
|
||||
android:layout_marginRight="8dp" />
|
||||
|
||||
<RelativeLayout
|
||||
android:onClick="@{scrollToBottomClickListener}"
|
||||
android:visibility="@{viewModel.isUserScrollingUp ? View.VISIBLE : View.GONE, default=gone}"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_above="@id/footer"
|
||||
android:layout_alignParentEnd="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="@drawable/round_button_background"
|
||||
android:padding="13dp"
|
||||
android:src="@drawable/scroll_to_bottom" />
|
||||
|
||||
<TextView
|
||||
style="@style/unread_count_font"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:background="@{viewModel.unreadMessagesCount == 0 ? @drawable/hidden_unread_message_count_bg : @drawable/unread_message_count_bg, default=@drawable/unread_message_count_bg}"
|
||||
android:gravity="center"
|
||||
android:text="@{viewModel.unreadMessagesCount == 0 ? `` : String.valueOf(viewModel.unreadMessagesCount), default=1}"
|
||||
android:translationY="@{viewModel.chatUnreadCountTranslateY}" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</layout>
|
||||
|
|
Loading…
Reference in a new issue