Split ConversationViewModel in two

This commit is contained in:
Sylvain Berfini 2023-11-10 11:18:47 +01:00
parent 8f33f1f0c9
commit 3071c079ba
8 changed files with 365 additions and 276 deletions

View file

@ -70,6 +70,7 @@ import org.linphone.ui.main.chat.model.ChatMessageReactionsModel
import org.linphone.ui.main.chat.view.RichEditText
import org.linphone.ui.main.chat.viewmodel.ConversationViewModel
import org.linphone.ui.main.chat.viewmodel.ConversationViewModel.Companion.SCROLLING_POSITION_NOT_SET
import org.linphone.ui.main.chat.viewmodel.SendMessageInConversationViewModel
import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.utils.AppUtils
import org.linphone.utils.Event
@ -90,6 +91,8 @@ class ConversationFragment : GenericFragment() {
private lateinit var viewModel: ConversationViewModel
private lateinit var sendMessageViewModel: SendMessageInConversationViewModel
private lateinit var adapter: ConversationEventAdapter
private lateinit var bottomSheetAdapter: ChatMessageBottomSheetAdapter
@ -107,7 +110,7 @@ class ConversationFragment : GenericFragment() {
Log.i("$TAG Picked file [$uri] matching path is [$path]")
if (path != null) {
withContext(Dispatchers.Main) {
viewModel.addAttachment(path)
sendMessageViewModel.addAttachment(path)
}
}
}
@ -139,12 +142,13 @@ class ConversationFragment : GenericFragment() {
}
override fun afterTextChanged(p0: Editable?) {
viewModel.isParticipantsListOpen.value = false
sendMessageViewModel.isParticipantsListOpen.value = false
val split = p0.toString().split(" ")
for (part in split) {
if (part == "@") {
viewModel.isParticipantsListOpen.value = true
Log.i("$TAG '@' found, opening participants list")
sendMessageViewModel.isParticipantsListOpen.value = true
}
}
}
@ -182,7 +186,10 @@ class ConversationFragment : GenericFragment() {
binding.lifecycleOwner = viewLifecycleOwner
viewModel = ViewModelProvider(this)[ConversationViewModel::class.java]
sendMessageViewModel = ViewModelProvider(this)[SendMessageInConversationViewModel::class.java]
binding.viewModel = viewModel
binding.sendMessageViewModel = sendMessageViewModel
binding.setBackClickListener {
goBack()
@ -212,12 +219,13 @@ class ConversationFragment : GenericFragment() {
goBack()
// TODO: show toast
}
} else {
sendMessageViewModel.configureChatRoom(viewModel.chatRoom)
}
}
}
viewModel.events.observe(viewLifecycleOwner) { items ->
val currentCount = adapter.itemCount
adapter.submitList(items)
Log.i("$TAG Events (messages) list updated with [${items.size}] items")
@ -298,13 +306,13 @@ class ConversationFragment : GenericFragment() {
}
}
viewModel.emojiToAddEvent.observe(viewLifecycleOwner) {
sendMessageViewModel.emojiToAddEvent.observe(viewLifecycleOwner) {
it.consume { emoji ->
binding.sendArea.messageToSend.addCharacterAtPosition(emoji)
}
}
viewModel.participantUsernameToAddEvent.observe(viewLifecycleOwner) {
sendMessageViewModel.participantUsernameToAddEvent.observe(viewLifecycleOwner) {
it.consume { username ->
Log.i("$TAG Adding username [$username] after '@'")
// Also add a space for convenience
@ -312,16 +320,16 @@ class ConversationFragment : GenericFragment() {
}
}
viewModel.searchFilter.observe(viewLifecycleOwner) { filter ->
viewModel.applyFilter(filter.trim())
}
viewModel.requestKeyboardHidingEvent.observe(viewLifecycleOwner) {
sendMessageViewModel.requestKeyboardHidingEvent.observe(viewLifecycleOwner) {
it.consume {
binding.search.hideKeyboard()
}
}
viewModel.searchFilter.observe(viewLifecycleOwner) { filter ->
viewModel.applyFilter(filter.trim())
}
viewModel.focusSearchBarEvent.observe(viewLifecycleOwner) {
it.consume { show ->
if (show) {
@ -346,6 +354,13 @@ class ConversationFragment : GenericFragment() {
}
}
viewModel.isGroup.observe(viewLifecycleOwner) { group ->
if (group) {
Log.i("$TAG Adding text observer to chat message sending area")
binding.sendArea.messageToSend.addTextChangedListener(textObserver)
}
}
viewModel.conferenceToJoinEvent.observe(viewLifecycleOwner) {
it.consume { conferenceUri ->
Log.i("$TAG Requesting to go to waiting room for conference URI [$conferenceUri]")
@ -378,7 +393,7 @@ class ConversationFragment : GenericFragment() {
Log.i("$TAG Rich content URI [$uri] matching path is [$path]")
if (path != null) {
withContext(Dispatchers.Main) {
viewModel.addAttachment(path)
sendMessageViewModel.addAttachment(path)
}
}
}
@ -390,7 +405,7 @@ class ConversationFragment : GenericFragment() {
if (files.isNotEmpty()) {
Log.i("$TAG Found [${files.size}] files to share from intent")
for (path in files) {
viewModel.addAttachment(path)
sendMessageViewModel.addAttachment(path)
}
sharedViewModel.filesToShareFromIntent.value = arrayListOf()
@ -401,13 +416,13 @@ class ConversationFragment : GenericFragment() {
RichEditText.RichEditTextSendListener {
override fun onControlEnterPressedAndReleased() {
Log.i("$TAG Detected left control + enter key presses, sending message")
viewModel.sendMessage()
sendMessageViewModel.sendMessage()
}
})
binding.root.setKeyboardInsetListener { keyboardVisible ->
if (keyboardVisible) {
viewModel.isEmojiPickerOpen.value = false
sendMessageViewModel.isEmojiPickerOpen.value = false
}
}
}
@ -430,10 +445,6 @@ 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() {
@ -497,7 +508,7 @@ class ConversationFragment : GenericFragment() {
layout.setReplyClickListener {
Log.i("$TAG Updating sending area to reply to selected message")
viewModel.replyToMessage(chatMessageModel)
sendMessageViewModel.replyToMessage(chatMessageModel)
dialog.dismiss()
}

View file

@ -23,12 +23,9 @@ import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.core.Address
import org.linphone.core.ChatMessage
import org.linphone.core.ChatRoom
import org.linphone.core.ChatRoomListenerStub
import org.linphone.core.EventLog
@ -37,12 +34,9 @@ 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.FileModel
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
import org.linphone.utils.FileUtils
import org.linphone.utils.LinphoneUtils
class ConversationViewModel @UiThread constructor() : ViewModel() {
@ -67,30 +61,10 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
val composingLabel = MutableLiveData<String>()
val textToSend = MutableLiveData<String>()
val searchBarVisible = MutableLiveData<Boolean>()
val searchFilter = MutableLiveData<String>()
val isEmojiPickerOpen = MutableLiveData<Boolean>()
val isParticipantsListOpen = MutableLiveData<Boolean>()
val participants = MutableLiveData<ArrayList<ParticipantModel>>()
val isFileAttachmentsListOpen = MutableLiveData<Boolean>()
val attachments = MutableLiveData<ArrayList<FileModel>>()
val isReplying = MutableLiveData<Boolean>()
val isReplyingTo = MutableLiveData<String>()
val isReplyingToMessage = MutableLiveData<String>()
val voiceRecordingInProgress = MutableLiveData<Boolean>()
var scrollingPosition: Int = SCROLLING_POSITION_NOT_SET
val requestKeyboardHidingEvent: MutableLiveData<Event<Boolean>> by lazy {
@ -113,20 +87,10 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
MutableLiveData<Event<String>>()
}
val emojiToAddEvent: MutableLiveData<Event<String>> by lazy {
MutableLiveData<Event<String>>()
}
val participantUsernameToAddEvent: MutableLiveData<Event<String>> by lazy {
MutableLiveData<Event<String>>()
}
val chatRoomFoundEvent = MutableLiveData<Event<Boolean>>()
lateinit var chatRoom: ChatRoom
private var chatMessageToReplyTo: ChatMessage? = null
private val chatRoomListener = object : ChatRoomListenerStub() {
@WorkerThread
override fun onChatMessageSending(chatRoom: ChatRoom, eventLog: EventLog) {
@ -204,32 +168,15 @@ 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 {
searchBarVisible.value = false
isEmojiPickerOpen.value = false
}
override fun onCleared() {
super.onCleared()
viewModelScope.launch {
for (file in attachments.value.orEmpty()) {
file.deleteFile()
}
}
coreContext.postOnCoreThread {
if (::chatRoom.isInitialized) {
chatRoom.removeListener(chatRoomListener)
@ -319,96 +266,6 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
}
}
@UiThread
fun toggleEmojiPickerVisibility() {
isEmojiPickerOpen.value = isEmojiPickerOpen.value == false
if (isEmojiPickerOpen.value == true) {
requestKeyboardHidingEvent.value = Event(true)
}
}
@UiThread
fun insertEmoji(emoji: String) {
emojiToAddEvent.value = Event(emoji)
}
@UiThread
fun replyToMessage(model: ChatMessageModel) {
coreContext.postOnCoreThread {
val message = model.chatMessage
Log.i("$TAG Pending reply to chat message [${message.messageId}]")
chatMessageToReplyTo = message
isReplyingTo.postValue(model.avatarModel.friend.name)
isReplyingToMessage.postValue(LinphoneUtils.getTextDescribingMessage(message))
isReplying.postValue(true)
}
}
@UiThread
fun cancelReply() {
Log.i("$TAG Cancelling reply")
isReplying.value = false
chatMessageToReplyTo = null
}
@UiThread
fun sendMessage() {
coreContext.postOnCoreThread {
val messageToReplyTo = chatMessageToReplyTo
val message = if (messageToReplyTo != null) {
Log.i("$TAG Sending message as reply to [${messageToReplyTo.messageId}]")
chatRoom.createReplyMessage(messageToReplyTo)
} else {
chatRoom.createEmptyMessage()
}
val toSend = textToSend.value.orEmpty().trim()
if (toSend.isNotEmpty()) {
message.addUtf8TextContent(toSend)
}
for (attachment in attachments.value.orEmpty()) {
val content = Factory.instance().createContent()
content.type = when (attachment.mimeType) {
FileUtils.MimeType.Image -> "image"
FileUtils.MimeType.Audio -> "audio"
FileUtils.MimeType.Video -> "video"
FileUtils.MimeType.Pdf -> "application"
FileUtils.MimeType.PlainText -> "text"
else -> "file"
}
content.subtype = if (attachment.mimeType == FileUtils.MimeType.PlainText) {
"plain"
} else {
FileUtils.getExtensionFromFileName(attachment.fileName)
}
content.name = attachment.fileName
content.filePath = attachment.file // Let the file body handler take care of the upload
message.addFileContent(content)
}
if (message.contents.isNotEmpty()) {
Log.i("$TAG Sending message")
message.send()
}
Log.i("$TAG Message sent, re-setting defaults")
textToSend.postValue("")
isReplying.postValue(false)
isFileAttachmentsListOpen.postValue(false)
isParticipantsListOpen.postValue(false)
isEmojiPickerOpen.postValue(false)
// Warning: do not delete files
val attachmentsList = arrayListOf<FileModel>()
attachments.postValue(attachmentsList)
chatMessageToReplyTo = null
}
}
@UiThread
fun deleteChatMessage(chatMessageModel: ChatMessageModel) {
coreContext.postOnCoreThread {
@ -428,85 +285,6 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
}
}
@UiThread
fun closeParticipantsList() {
isParticipantsListOpen.value = false
}
@UiThread
fun closeFileAttachmentsList() {
viewModelScope.launch {
for (file in attachments.value.orEmpty()) {
file.deleteFile()
}
}
val list = arrayListOf<FileModel>()
attachments.value = list
isFileAttachmentsListOpen.value = false
}
@UiThread
fun addAttachment(file: String) {
val list = arrayListOf<FileModel>()
list.addAll(attachments.value.orEmpty())
val model = FileModel(file) { file ->
removeAttachment(file)
}
list.add(model)
attachments.value = list
if (list.isNotEmpty()) {
isFileAttachmentsListOpen.value = true
}
}
@UiThread
fun removeAttachment(file: String, delete: Boolean = true) {
val list = arrayListOf<FileModel>()
list.addAll(attachments.value.orEmpty())
val found = list.find {
it.file == file
}
if (found != null) {
if (delete) {
viewModelScope.launch {
found.deleteFile()
}
}
list.remove(found)
} else {
Log.w("$TAG Failed to find file attachment matching [$file]")
}
attachments.value = list
if (list.isEmpty()) {
isFileAttachmentsListOpen.value = false
}
}
@UiThread
fun startVoiceMessageRecording() {
voiceRecordingInProgress.value = true
}
@UiThread
fun stopVoiceMessageRecording() {
}
@UiThread
fun cancelVoiceMessageRecording() {
voiceRecordingInProgress.value = false
}
@UiThread
fun playVoiceMessageRecording() {
}
@UiThread
fun pauseVoiceMessageRecording() {
}
@WorkerThread
private fun configureChatRoom() {
scrollingPosition = SCROLLING_POSITION_NOT_SET
@ -551,7 +329,6 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
computeEvents()
chatRoom.markAsRead()
computeParticipantsList()
}
@WorkerThread
@ -686,24 +463,4 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
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)
}
}

View file

@ -0,0 +1,312 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.ui.main.chat.viewmodel
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.ChatMessage
import org.linphone.core.ChatRoom
import org.linphone.core.ChatRoomListenerStub
import org.linphone.core.EventLog
import org.linphone.core.Factory
import org.linphone.core.tools.Log
import org.linphone.ui.main.chat.model.ChatMessageModel
import org.linphone.ui.main.chat.model.FileModel
import org.linphone.ui.main.chat.model.ParticipantModel
import org.linphone.utils.Event
import org.linphone.utils.FileUtils
import org.linphone.utils.LinphoneUtils
class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() {
companion object {
private const val TAG = "[Send Message In Conversation ViewModel]"
}
val textToSend = MutableLiveData<String>()
val isEmojiPickerOpen = MutableLiveData<Boolean>()
val isParticipantsListOpen = MutableLiveData<Boolean>()
val participants = MutableLiveData<ArrayList<ParticipantModel>>()
val isFileAttachmentsListOpen = MutableLiveData<Boolean>()
val attachments = MutableLiveData<ArrayList<FileModel>>()
val isReplying = MutableLiveData<Boolean>()
val isReplyingTo = MutableLiveData<String>()
val isReplyingToMessage = MutableLiveData<String>()
val voiceRecordingInProgress = MutableLiveData<Boolean>()
val requestKeyboardHidingEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
val emojiToAddEvent: MutableLiveData<Event<String>> by lazy {
MutableLiveData<Event<String>>()
}
val participantUsernameToAddEvent: MutableLiveData<Event<String>> by lazy {
MutableLiveData<Event<String>>()
}
lateinit var chatRoom: ChatRoom
private var chatMessageToReplyTo: ChatMessage? = null
private val chatRoomListener = object : ChatRoomListenerStub() {
@WorkerThread
override fun onParticipantAdded(chatRoom: ChatRoom, eventLog: EventLog) {
computeParticipantsList()
}
@WorkerThread
override fun onParticipantRemoved(chatRoom: ChatRoom, eventLog: EventLog) {
computeParticipantsList()
}
}
init {
isEmojiPickerOpen.value = false
}
override fun onCleared() {
super.onCleared()
viewModelScope.launch {
for (file in attachments.value.orEmpty()) {
file.deleteFile()
}
}
coreContext.postOnCoreThread {
if (::chatRoom.isInitialized) {
chatRoom.removeListener(chatRoomListener)
}
}
}
@UiThread
fun configureChatRoom(room: ChatRoom) {
chatRoom = room
coreContext.postOnCoreThread {
chatRoom.addListener(chatRoomListener)
computeParticipantsList()
}
}
@UiThread
fun toggleEmojiPickerVisibility() {
isEmojiPickerOpen.value = isEmojiPickerOpen.value == false
if (isEmojiPickerOpen.value == true) {
requestKeyboardHidingEvent.value = Event(true)
}
}
@UiThread
fun insertEmoji(emoji: String) {
emojiToAddEvent.value = Event(emoji)
}
@UiThread
fun replyToMessage(model: ChatMessageModel) {
coreContext.postOnCoreThread {
val message = model.chatMessage
Log.i("$TAG Pending reply to chat message [${message.messageId}]")
chatMessageToReplyTo = message
isReplyingTo.postValue(model.avatarModel.friend.name)
isReplyingToMessage.postValue(LinphoneUtils.getTextDescribingMessage(message))
isReplying.postValue(true)
}
}
@UiThread
fun cancelReply() {
Log.i("$TAG Cancelling reply")
isReplying.value = false
chatMessageToReplyTo = null
}
@UiThread
fun sendMessage() {
coreContext.postOnCoreThread {
val messageToReplyTo = chatMessageToReplyTo
val message = if (messageToReplyTo != null) {
Log.i("$TAG Sending message as reply to [${messageToReplyTo.messageId}]")
chatRoom.createReplyMessage(messageToReplyTo)
} else {
chatRoom.createEmptyMessage()
}
val toSend = textToSend.value.orEmpty().trim()
if (toSend.isNotEmpty()) {
message.addUtf8TextContent(toSend)
}
for (attachment in attachments.value.orEmpty()) {
val content = Factory.instance().createContent()
content.type = when (attachment.mimeType) {
FileUtils.MimeType.Image -> "image"
FileUtils.MimeType.Audio -> "audio"
FileUtils.MimeType.Video -> "video"
FileUtils.MimeType.Pdf -> "application"
FileUtils.MimeType.PlainText -> "text"
else -> "file"
}
content.subtype = if (attachment.mimeType == FileUtils.MimeType.PlainText) {
"plain"
} else {
FileUtils.getExtensionFromFileName(attachment.fileName)
}
content.name = attachment.fileName
content.filePath = attachment.file // Let the file body handler take care of the upload
message.addFileContent(content)
}
if (message.contents.isNotEmpty()) {
Log.i("$TAG Sending message")
message.send()
}
Log.i("$TAG Message sent, re-setting defaults")
textToSend.postValue("")
isReplying.postValue(false)
isFileAttachmentsListOpen.postValue(false)
isParticipantsListOpen.postValue(false)
isEmojiPickerOpen.postValue(false)
// Warning: do not delete files
val attachmentsList = arrayListOf<FileModel>()
attachments.postValue(attachmentsList)
chatMessageToReplyTo = null
}
}
@UiThread
fun closeParticipantsList() {
isParticipantsListOpen.value = false
}
@UiThread
fun closeFileAttachmentsList() {
viewModelScope.launch {
for (file in attachments.value.orEmpty()) {
file.deleteFile()
}
}
val list = arrayListOf<FileModel>()
attachments.value = list
isFileAttachmentsListOpen.value = false
}
@UiThread
fun addAttachment(file: String) {
val list = arrayListOf<FileModel>()
list.addAll(attachments.value.orEmpty())
val model = FileModel(file) { file ->
removeAttachment(file)
}
list.add(model)
attachments.value = list
if (list.isNotEmpty()) {
isFileAttachmentsListOpen.value = true
}
}
@UiThread
fun removeAttachment(file: String, delete: Boolean = true) {
val list = arrayListOf<FileModel>()
list.addAll(attachments.value.orEmpty())
val found = list.find {
it.file == file
}
if (found != null) {
if (delete) {
viewModelScope.launch {
found.deleteFile()
}
}
list.remove(found)
} else {
Log.w("$TAG Failed to find file attachment matching [$file]")
}
attachments.value = list
if (list.isEmpty()) {
isFileAttachmentsListOpen.value = false
}
}
@UiThread
fun startVoiceMessageRecording() {
voiceRecordingInProgress.value = true
}
@UiThread
fun stopVoiceMessageRecording() {
}
@UiThread
fun cancelVoiceMessageRecording() {
voiceRecordingInProgress.value = false
}
@UiThread
fun playVoiceMessageRecording() {
}
@UiThread
fun pauseVoiceMessageRecording() {
}
@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)
}
}

View file

@ -6,7 +6,7 @@
<import type="android.view.View" />
<variable
name="viewModel"
type="org.linphone.ui.main.chat.viewmodel.ConversationViewModel" />
type="org.linphone.ui.main.chat.viewmodel.SendMessageInConversationViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout

View file

@ -24,6 +24,9 @@
<variable
name="viewModel"
type="org.linphone.ui.main.chat.viewmodel.ConversationViewModel" />
<variable
name="sendMessageViewModel"
type="org.linphone.ui.main.chat.viewmodel.SendMessageInConversationViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
@ -231,7 +234,7 @@
<include
android:id="@+id/send_area"
openFilePickerClickListener="@{openFilePickerClickListener}"
viewModel="@{viewModel}"
viewModel="@{sendMessageViewModel}"
android:visibility="@{viewModel.isReadOnly ? View.GONE : View.VISIBLE}"
layout="@layout/chat_conversation_send_area_bottom_sheet"/>

View file

@ -6,7 +6,7 @@
<import type="android.view.View" />
<variable
name="viewModel"
type="org.linphone.ui.main.chat.viewmodel.ConversationViewModel" />
type="org.linphone.ui.main.chat.viewmodel.SendMessageInConversationViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout

View file

@ -6,7 +6,7 @@
<import type="android.view.View" />
<variable
name="viewModel"
type="org.linphone.ui.main.chat.viewmodel.ConversationViewModel" />
type="org.linphone.ui.main.chat.viewmodel.SendMessageInConversationViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout

View file

@ -10,7 +10,7 @@
type="View.OnClickListener" />
<variable
name="viewModel"
type="org.linphone.ui.main.chat.viewmodel.ConversationViewModel" />
type="org.linphone.ui.main.chat.viewmodel.SendMessageInConversationViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
@ -20,6 +20,20 @@
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<!-- Keep behavior to have it at the bottom -->
<androidx.constraintlayout.widget.Group
android:id="@+id/voice_recording"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{viewModel.voiceRecordingInProgress ? View.VISIBLE : View.GONE, default=gone}"
app:constraint_referenced_ids="cancel_voice_message, voice_record_progress, stop_recording, voice_recording_length" />
<androidx.constraintlayout.widget.Group
android:id="@+id/standard_messages"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{viewModel.voiceRecordingInProgress ? View.INVISIBLE : View.VISIBLE}"
app:constraint_referenced_ids="emoji_picker_toggle, attach_file, message_area_background, message_to_send" />
<include
android:id="@+id/reply_area"
layout="@layout/chat_conversation_reply_area"
@ -69,7 +83,6 @@
android:onClick="@{() -> viewModel.toggleEmojiPickerVisibility()}"
android:padding="8dp"
android:src="@{viewModel.isEmojiPickerOpen ? @drawable/x : @drawable/smiley, default=@drawable/smiley}"
android:visibility="@{viewModel.voiceRecordingInProgress ? View.GONE : View.VISIBLE}"
app:layout_constraintBottom_toBottomOf="@id/message_area_background"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/message_area_background"
@ -84,7 +97,6 @@
android:padding="8dp"
android:src="@drawable/x"
android:background="@drawable/circle_white_button_background"
android:visibility="@{viewModel.voiceRecordingInProgress ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintBottom_toBottomOf="@id/message_area_background"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/message_area_background"
@ -99,7 +111,6 @@
android:onClick="@{openFilePickerClickListener}"
android:padding="8dp"
android:src="@drawable/paperclip"
android:visibility="@{viewModel.voiceRecordingInProgress ? View.GONE : View.VISIBLE}"
app:layout_constraintBottom_toBottomOf="@id/message_area_background"
app:layout_constraintEnd_toStartOf="@id/message_area_background"
app:layout_constraintStart_toEndOf="@id/emoji_picker_toggle"
@ -112,7 +123,6 @@
android:layout_height="0dp"
android:layout_marginEnd="16dp"
android:src="@drawable/edit_text_background"
android:visibility="@{viewModel.voiceRecordingInProgress ? View.INVISIBLE : View.VISIBLE}"
app:layout_constraintBottom_toBottomOf="@id/message_to_send"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/attach_file"
@ -136,7 +146,6 @@
android:text="@={viewModel.textToSend}"
android:textColorHint="@color/gray_main2_400"
android:textSize="14sp"
android:visibility="@{viewModel.voiceRecordingInProgress ? View.INVISIBLE : View.VISIBLE}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/send_barrier"
app:layout_constraintStart_toStartOf="@id/message_area_background"
@ -157,7 +166,6 @@
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:progressDrawable="@drawable/voice_recording_gradient_progress"
android:visibility="@{viewModel.voiceRecordingInProgress ? View.VISIBLE : View.GONE, default=gone}"
tools:progress="60"
app:layout_constraintBottom_toBottomOf="@id/message_to_send"
app:layout_constraintEnd_toStartOf="@id/send_message"
@ -173,7 +181,6 @@
android:padding="8dp"
android:src="@drawable/stop_fill"
android:background="@drawable/circle_white_button_background"
android:visibility="@{viewModel.voiceRecordingInProgress ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintBottom_toBottomOf="@id/voice_record_progress"
app:layout_constraintStart_toStartOf="@id/voice_record_progress"
app:layout_constraintTop_toTopOf="@id/voice_record_progress"
@ -193,7 +200,6 @@
android:textSize="14sp"
android:textColor="@color/gray_main2_600"
android:background="@drawable/shape_squircle_white_r50_background"
android:visibility="@{viewModel.voiceRecordingInProgress ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintBottom_toBottomOf="@id/voice_record_progress"
app:layout_constraintEnd_toEndOf="@id/voice_record_progress"
app:layout_constraintTop_toTopOf="@id/voice_record_progress"/>