From 338c1367785f718f2d17f3e1824c0cb12d35bc03 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 19 Oct 2021 17:25:04 +0200 Subject: [PATCH] Add scroll to bottom & unread chat message counter in chat room while scrolling up in history --- CHANGELOG.md | 1 + .../main/chat/ChatScrollListener.kt | 15 ++++++++ .../chat/fragments/DetailChatRoomFragment.kt | 31 +++++++++++++++- .../viewmodels/ChatMessagesListViewModel.kt | 6 ++- .../main/chat/viewmodels/ChatRoomViewModel.kt | 23 ++++++++++++ .../notifications/NotificationsManager.kt | 4 +- .../scroll_to_bottom_default.png | Bin 0 -> 15058 bytes .../main/res/drawable/scroll_to_bottom.xml | 20 ++++++++++ .../res/layout/chat_room_detail_fragment.xml | 35 ++++++++++++++++++ 9 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 app/src/main/res/drawable-xhdpi/scroll_to_bottom_default.png create mode 100644 app/src/main/res/drawable/scroll_to_bottom.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 73dae8c45..b021ada36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/app/src/main/java/org/linphone/activities/main/chat/ChatScrollListener.kt b/app/src/main/java/org/linphone/activities/main/chat/ChatScrollListener.kt index 7b2598b83..21a97ebcf 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/ChatScrollListener.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/ChatScrollListener.kt @@ -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. diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index 10882e4ae..39b9a6219 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -75,8 +75,15 @@ class DetailChatRoomFragment : MasterFragment 0) { + binding.chatMessagesList.smoothScrollToPosition(adapter.itemCount - 1) + } + } + private fun pickFile() { val intentsList = ArrayList() diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessagesListViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessagesListViewModel.kt index 8aa80622a..b030e600b 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessagesListViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessagesListViewModel.kt @@ -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 { val list = arrayListOf() - 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)) } diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt index 6e16530c3..5194d5f08 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt @@ -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() } + val isUserScrollingUp = MutableLiveData() + var oneParticipantOneDevice: Boolean = false var addressToCall: Address? = null var onlyParticipantOnlyDeviceAddress: Address? = null + val chatUnreadCountTranslateY = MutableLiveData() + + 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 { diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index 397295dec..39c2cb6ed 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -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 } diff --git a/app/src/main/res/drawable-xhdpi/scroll_to_bottom_default.png b/app/src/main/res/drawable-xhdpi/scroll_to_bottom_default.png new file mode 100644 index 0000000000000000000000000000000000000000..3be410ae19403a8e7c8f51cdddb080cae1ff202f GIT binary patch literal 15058 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 + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/chat_room_detail_fragment.xml b/app/src/main/res/layout/chat_room_detail_fragment.xml index 431c40baa..89ff8d976 100644 --- a/app/src/main/res/layout/chat_room_detail_fragment.xml +++ b/app/src/main/res/layout/chat_room_detail_fragment.xml @@ -26,6 +26,9 @@ + @@ -297,6 +300,38 @@ android:layout_marginTop="8dp" android:layout_marginRight="8dp" /> + + + + + + + +