From dab462de35ba22ad2f795a48faae63c9de1d6a3a Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 8 Nov 2023 16:42:38 +0100 Subject: [PATCH] Fixed chat rooms list sort order + added mentions menu when typing '@' + hide participants in non-group conversation --- .../chat/fragment/ConversationFragment.kt | 39 ++++++++++++++++++ .../ui/main/chat/model/ParticipantModel.kt | 10 ++++- .../viewmodel/ConversationInfoViewModel.kt | 5 ++- .../chat/viewmodel/ConversationViewModel.kt | 40 +++++++++++++++++++ .../viewmodel/ConversationsListViewModel.kt | 2 +- .../res/layout/chat_conversation_fragment.xml | 23 ++++++++++- .../main/res/layout/chat_info_fragment.xml | 8 +++- .../res/layout/chat_participant_list_cell.xml | 1 + 8 files changed, 120 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt index 201904003..8d4484dcd 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt @@ -30,6 +30,8 @@ import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.net.Uri import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -39,6 +41,7 @@ import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.UiThread import androidx.core.view.doOnPreDraw +import androidx.core.widget.addTextChangedListener import androidx.databinding.DataBindingUtil import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope @@ -70,6 +73,7 @@ import org.linphone.ui.main.fragment.GenericFragment import org.linphone.utils.AppUtils import org.linphone.utils.Event import org.linphone.utils.LinphoneUtils +import org.linphone.utils.addCharacterAtPosition import org.linphone.utils.hideKeyboard import org.linphone.utils.setKeyboardInsetListener import org.linphone.utils.showKeyboard @@ -114,6 +118,25 @@ class ConversationFragment : GenericFragment() { } } + private val textObserver = object : TextWatcher { + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + } + + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + } + + override fun afterTextChanged(p0: Editable?) { + viewModel.isParticipantsListOpen.value = false + + val split = p0.toString().split(" ") + for (part in split) { + if (part == "@") { + viewModel.isParticipantsListOpen.value = true + } + } + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -262,6 +285,14 @@ class ConversationFragment : GenericFragment() { findNavController().navigate(action) } + viewModel.participantUsernameToAddEvent.observe(viewLifecycleOwner) { + it.consume { username -> + Log.i("$TAG Adding username [$username] after '@'") + // Also add a space for convenience + binding.sendArea.messageToSend.addCharacterAtPosition("$username ") + } + } + viewModel.searchFilter.observe(viewLifecycleOwner) { filter -> viewModel.applyFilter(filter.trim()) } @@ -337,9 +368,17 @@ class ConversationFragment : GenericFragment() { } catch (e: IllegalStateException) { Log.e("$TAG Failed to register data observer to adapter: $e") } + + if (viewModel.isGroup.value == true) { + binding.sendArea.messageToSend.addTextChangedListener(textObserver) + } } override fun onPause() { + if (viewModel.isGroup.value == true) { + binding.sendArea.messageToSend.removeTextChangedListener(textObserver) + } + try { adapter.unregisterAdapterDataObserver(dataObserver) } catch (e: IllegalStateException) { diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/ParticipantModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/ParticipantModel.kt index 102e78d5a..894ed1953 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/ParticipantModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/ParticipantModel.kt @@ -27,14 +27,20 @@ import org.linphone.core.Address class ParticipantModel @WorkerThread constructor( val address: Address, - val isMyselfAdmin: Boolean, - val isParticipantAdmin: Boolean, + val isMyselfAdmin: Boolean = false, + val isParticipantAdmin: Boolean = false, + private val onClicked: ((model: ParticipantModel) -> Unit)? = null, private val onMenuClicked: ((view: View, model: ParticipantModel) -> Unit)? = null ) { val sipUri = address.asStringUriOnly() val avatarModel = coreContext.contactsManager.getContactAvatarModelForAddress(address) + @UiThread + fun onClicked() { + onClicked?.invoke(this) + } + @UiThread fun openMenu(view: View) { onMenuClicked?.invoke(view, this) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt index 7270456a1..0fb73772d 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt @@ -362,10 +362,10 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() { } else { for (participant in chatRoom.participants) { val isParticipantAdmin = if (groupChatRoom) participant.isAdmin else false - val model = ParticipantModel(participant.address, selfAdmin, isParticipantAdmin) { view, model -> + val model = ParticipantModel(participant.address, selfAdmin, isParticipantAdmin, onMenuClicked = { view, model -> // openMenu showParticipantAdminPopupMenuEvent.postValue(Event(Pair(view, model))) - } + }) friends.add(model.avatarModel.friend) participantsList.add(model) } @@ -374,6 +374,7 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() { val avatar = if (groupChatRoom) { val fakeFriend = coreContext.core.createFriend() val model = ContactAvatarModel(fakeFriend) + model.defaultToConferenceIcon.postValue(true) model.setPicturesFromFriends(friends) model } else { diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt index 1bc019ef1..1db564c16 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt @@ -35,6 +35,7 @@ import org.linphone.core.Friend import org.linphone.core.tools.Log import org.linphone.ui.main.chat.model.ChatMessageModel import org.linphone.ui.main.chat.model.EventLogModel +import org.linphone.ui.main.chat.model.ParticipantModel import org.linphone.ui.main.contacts.model.ContactAvatarModel import org.linphone.utils.AppUtils import org.linphone.utils.Event @@ -70,6 +71,14 @@ class ConversationViewModel @UiThread constructor() : ViewModel() { val isEmojiPickerOpen = MutableLiveData() + val isParticipantsListOpen = MutableLiveData() + + val participants = MutableLiveData>() + + val participantUsernameToAddEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + val isReplying = MutableLiveData() val isReplyingTo = MutableLiveData() @@ -181,6 +190,16 @@ class ConversationViewModel @UiThread constructor() : ViewModel() { events.postValue(list) } + + @WorkerThread + override fun onParticipantAdded(chatRoom: ChatRoom, eventLog: EventLog) { + computeParticipantsList() + } + + @WorkerThread + override fun onParticipantRemoved(chatRoom: ChatRoom, eventLog: EventLog) { + computeParticipantsList() + } } init { @@ -403,6 +422,7 @@ class ConversationViewModel @UiThread constructor() : ViewModel() { computeEvents() chatRoom.markAsRead() + computeParticipantsList() } @WorkerThread @@ -538,4 +558,24 @@ class ConversationViewModel @UiThread constructor() : ViewModel() { composingLabel.postValue("") } } + + @WorkerThread + private fun computeParticipantsList() { + val participantsList = arrayListOf() + + for (participant in chatRoom.participants) { + val model = ParticipantModel(participant.address, onClicked = { clicked -> + Log.i("$TAG Clicked on participant [${clicked.sipUri}]") + coreContext.postOnCoreThread { + val username = clicked.address.username + if (!username.isNullOrEmpty()) { + participantUsernameToAddEvent.postValue(Event(username)) + } + } + }) + participantsList.add(model) + } + + participants.postValue(participantsList) + } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt index 802a09d97..d424afcf1 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt @@ -171,7 +171,7 @@ class ConversationsListViewModel @UiThread constructor() : AbstractTopBarViewMod Log.i("$TAG Re-ordering chat rooms") val sortedList = arrayListOf() sortedList.addAll(conversations.value.orEmpty()) - sortedList.sortBy { + sortedList.sortByDescending { it.lastUpdateTime.value } conversations.postValue(sortedList) diff --git a/app/src/main/res/layout/chat_conversation_fragment.xml b/app/src/main/res/layout/chat_conversation_fragment.xml index 68f1c9ac3..cb79e92d8 100644 --- a/app/src/main/res/layout/chat_conversation_fragment.xml +++ b/app/src/main/res/layout/chat_conversation_fragment.xml @@ -223,10 +223,31 @@ android:textColor="@color/gray_main2_400" android:visibility="@{viewModel.composingLabel.length() == 0 ? View.GONE : View.VISIBLE}" app:layout_constraintTop_toBottomOf="@id/events_list" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/participants" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> + + + + + + @@ -215,6 +216,7 @@ android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:src="@drawable/shape_squircle_white_background" + android:visibility="@{viewModel.isGroup ? View.VISIBLE : View.GONE}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@id/participants" @@ -222,13 +224,14 @@