mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-04-30 18:56:23 +00:00
Fixed chat rooms list sort order + added mentions menu when typing '@' + hide participants in non-group conversation
This commit is contained in:
parent
d895fc6a09
commit
dab462de35
8 changed files with 120 additions and 8 deletions
|
|
@ -30,6 +30,8 @@ import android.graphics.drawable.ColorDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
|
@ -39,6 +41,7 @@ import androidx.activity.result.PickVisualMediaRequest
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.annotation.UiThread
|
import androidx.annotation.UiThread
|
||||||
import androidx.core.view.doOnPreDraw
|
import androidx.core.view.doOnPreDraw
|
||||||
|
import androidx.core.widget.addTextChangedListener
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
|
@ -70,6 +73,7 @@ import org.linphone.ui.main.fragment.GenericFragment
|
||||||
import org.linphone.utils.AppUtils
|
import org.linphone.utils.AppUtils
|
||||||
import org.linphone.utils.Event
|
import org.linphone.utils.Event
|
||||||
import org.linphone.utils.LinphoneUtils
|
import org.linphone.utils.LinphoneUtils
|
||||||
|
import org.linphone.utils.addCharacterAtPosition
|
||||||
import org.linphone.utils.hideKeyboard
|
import org.linphone.utils.hideKeyboard
|
||||||
import org.linphone.utils.setKeyboardInsetListener
|
import org.linphone.utils.setKeyboardInsetListener
|
||||||
import org.linphone.utils.showKeyboard
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
|
@ -262,6 +285,14 @@ class ConversationFragment : GenericFragment() {
|
||||||
findNavController().navigate(action)
|
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.searchFilter.observe(viewLifecycleOwner) { filter ->
|
||||||
viewModel.applyFilter(filter.trim())
|
viewModel.applyFilter(filter.trim())
|
||||||
}
|
}
|
||||||
|
|
@ -337,9 +368,17 @@ class ConversationFragment : GenericFragment() {
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
Log.e("$TAG Failed to register data observer to adapter: $e")
|
Log.e("$TAG Failed to register data observer to adapter: $e")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (viewModel.isGroup.value == true) {
|
||||||
|
binding.sendArea.messageToSend.addTextChangedListener(textObserver)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
if (viewModel.isGroup.value == true) {
|
||||||
|
binding.sendArea.messageToSend.removeTextChangedListener(textObserver)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
adapter.unregisterAdapterDataObserver(dataObserver)
|
adapter.unregisterAdapterDataObserver(dataObserver)
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,20 @@ import org.linphone.core.Address
|
||||||
|
|
||||||
class ParticipantModel @WorkerThread constructor(
|
class ParticipantModel @WorkerThread constructor(
|
||||||
val address: Address,
|
val address: Address,
|
||||||
val isMyselfAdmin: Boolean,
|
val isMyselfAdmin: Boolean = false,
|
||||||
val isParticipantAdmin: Boolean,
|
val isParticipantAdmin: Boolean = false,
|
||||||
|
private val onClicked: ((model: ParticipantModel) -> Unit)? = null,
|
||||||
private val onMenuClicked: ((view: View, model: ParticipantModel) -> Unit)? = null
|
private val onMenuClicked: ((view: View, model: ParticipantModel) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
val sipUri = address.asStringUriOnly()
|
val sipUri = address.asStringUriOnly()
|
||||||
|
|
||||||
val avatarModel = coreContext.contactsManager.getContactAvatarModelForAddress(address)
|
val avatarModel = coreContext.contactsManager.getContactAvatarModelForAddress(address)
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
fun onClicked() {
|
||||||
|
onClicked?.invoke(this)
|
||||||
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
fun openMenu(view: View) {
|
fun openMenu(view: View) {
|
||||||
onMenuClicked?.invoke(view, this)
|
onMenuClicked?.invoke(view, this)
|
||||||
|
|
|
||||||
|
|
@ -362,10 +362,10 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() {
|
||||||
} else {
|
} else {
|
||||||
for (participant in chatRoom.participants) {
|
for (participant in chatRoom.participants) {
|
||||||
val isParticipantAdmin = if (groupChatRoom) participant.isAdmin else false
|
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
|
// openMenu
|
||||||
showParticipantAdminPopupMenuEvent.postValue(Event(Pair(view, model)))
|
showParticipantAdminPopupMenuEvent.postValue(Event(Pair(view, model)))
|
||||||
}
|
})
|
||||||
friends.add(model.avatarModel.friend)
|
friends.add(model.avatarModel.friend)
|
||||||
participantsList.add(model)
|
participantsList.add(model)
|
||||||
}
|
}
|
||||||
|
|
@ -374,6 +374,7 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() {
|
||||||
val avatar = if (groupChatRoom) {
|
val avatar = if (groupChatRoom) {
|
||||||
val fakeFriend = coreContext.core.createFriend()
|
val fakeFriend = coreContext.core.createFriend()
|
||||||
val model = ContactAvatarModel(fakeFriend)
|
val model = ContactAvatarModel(fakeFriend)
|
||||||
|
model.defaultToConferenceIcon.postValue(true)
|
||||||
model.setPicturesFromFriends(friends)
|
model.setPicturesFromFriends(friends)
|
||||||
model
|
model
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import org.linphone.core.Friend
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
import org.linphone.ui.main.chat.model.ChatMessageModel
|
import org.linphone.ui.main.chat.model.ChatMessageModel
|
||||||
import org.linphone.ui.main.chat.model.EventLogModel
|
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.ui.main.contacts.model.ContactAvatarModel
|
||||||
import org.linphone.utils.AppUtils
|
import org.linphone.utils.AppUtils
|
||||||
import org.linphone.utils.Event
|
import org.linphone.utils.Event
|
||||||
|
|
@ -70,6 +71,14 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
|
||||||
|
|
||||||
val isEmojiPickerOpen = MutableLiveData<Boolean>()
|
val isEmojiPickerOpen = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
|
val isParticipantsListOpen = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
|
val participants = MutableLiveData<ArrayList<ParticipantModel>>()
|
||||||
|
|
||||||
|
val participantUsernameToAddEvent: MutableLiveData<Event<String>> by lazy {
|
||||||
|
MutableLiveData<Event<String>>()
|
||||||
|
}
|
||||||
|
|
||||||
val isReplying = MutableLiveData<Boolean>()
|
val isReplying = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
val isReplyingTo = MutableLiveData<String>()
|
val isReplyingTo = MutableLiveData<String>()
|
||||||
|
|
@ -181,6 +190,16 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
|
||||||
|
|
||||||
events.postValue(list)
|
events.postValue(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
override fun onParticipantAdded(chatRoom: ChatRoom, eventLog: EventLog) {
|
||||||
|
computeParticipantsList()
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
override fun onParticipantRemoved(chatRoom: ChatRoom, eventLog: EventLog) {
|
||||||
|
computeParticipantsList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
@ -403,6 +422,7 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
|
||||||
|
|
||||||
computeEvents()
|
computeEvents()
|
||||||
chatRoom.markAsRead()
|
chatRoom.markAsRead()
|
||||||
|
computeParticipantsList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
|
|
@ -538,4 +558,24 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
|
||||||
composingLabel.postValue("")
|
composingLabel.postValue("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private fun computeParticipantsList() {
|
||||||
|
val participantsList = arrayListOf<ParticipantModel>()
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ class ConversationsListViewModel @UiThread constructor() : AbstractTopBarViewMod
|
||||||
Log.i("$TAG Re-ordering chat rooms")
|
Log.i("$TAG Re-ordering chat rooms")
|
||||||
val sortedList = arrayListOf<ConversationModel>()
|
val sortedList = arrayListOf<ConversationModel>()
|
||||||
sortedList.addAll(conversations.value.orEmpty())
|
sortedList.addAll(conversations.value.orEmpty())
|
||||||
sortedList.sortBy {
|
sortedList.sortByDescending {
|
||||||
it.lastUpdateTime.value
|
it.lastUpdateTime.value
|
||||||
}
|
}
|
||||||
conversations.postValue(sortedList)
|
conversations.postValue(sortedList)
|
||||||
|
|
|
||||||
|
|
@ -223,10 +223,31 @@
|
||||||
android:textColor="@color/gray_main2_400"
|
android:textColor="@color/gray_main2_400"
|
||||||
android:visibility="@{viewModel.composingLabel.length() == 0 ? View.GONE : View.VISIBLE}"
|
android:visibility="@{viewModel.composingLabel.length() == 0 ? View.GONE : View.VISIBLE}"
|
||||||
app:layout_constraintTop_toBottomOf="@id/events_list"
|
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_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:id="@+id/participants"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/shape_squircle_gray_100_background"
|
||||||
|
android:visibility="@{viewModel.isParticipantsListOpen ? View.VISIBLE : View.GONE, default=gone}"
|
||||||
|
app:layout_constraintHeight_max="300dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="5dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
entries="@{viewModel.participants}"
|
||||||
|
layout="@{@layout/chat_participant_list_cell}"/>
|
||||||
|
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<include
|
<include
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,7 @@
|
||||||
android:text="@string/conversation_info_participants_list_title"
|
android:text="@string/conversation_info_participants_list_title"
|
||||||
android:drawableEnd="@{viewModel.expandParticipants ? @drawable/caret_up : @drawable/caret_down, default=@drawable/caret_up}"
|
android:drawableEnd="@{viewModel.expandParticipants ? @drawable/caret_up : @drawable/caret_down, default=@drawable/caret_up}"
|
||||||
android:drawableTint="@color/gray_main2_600"
|
android:drawableTint="@color/gray_main2_600"
|
||||||
|
android:visibility="@{viewModel.isGroup ? View.VISIBLE : View.GONE}"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/mute_label"/>
|
app:layout_constraintTop_toBottomOf="@id/mute_label"/>
|
||||||
|
|
@ -215,6 +216,7 @@
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:src="@drawable/shape_squircle_white_background"
|
android:src="@drawable/shape_squircle_white_background"
|
||||||
|
android:visibility="@{viewModel.isGroup ? View.VISIBLE : View.GONE}"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@id/participants"
|
app:layout_constraintTop_toTopOf="@id/participants"
|
||||||
|
|
@ -222,13 +224,14 @@
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/participants"
|
android:id="@+id/participants"
|
||||||
android:visibility="@{viewModel.expandParticipants ? View.VISIBLE : View.GONE}"
|
android:visibility="@{viewModel.expandParticipants && viewModel.isGroup ? View.VISIBLE : View.GONE}"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
|
app:layout_constraintHeight_max="300dp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/participants_label"
|
app:layout_constraintTop_toBottomOf="@id/participants_label"
|
||||||
|
|
@ -240,6 +243,7 @@
|
||||||
android:onClick="@{addParticipantsClickListener}"
|
android:onClick="@{addParticipantsClickListener}"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
android:background="@drawable/tertiary_button_background"
|
android:background="@drawable/tertiary_button_background"
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="16dp"
|
||||||
android:paddingEnd="16dp"
|
android:paddingEnd="16dp"
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:onClick="@{() -> model.onClicked()}"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/primary_cell_background"
|
android:background="@drawable/primary_cell_background"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue