Reworked participants list for mentions a bit, added possibility to attach/remove files to/from sending area

This commit is contained in:
Sylvain Berfini 2023-11-09 12:41:26 +01:00
parent 713e048db9
commit 116ca3cbfe
14 changed files with 493 additions and 73 deletions

View file

@ -67,11 +67,13 @@ import org.linphone.ui.main.chat.adapter.ConversationEventAdapter
import org.linphone.ui.main.chat.model.ChatMessageDeliveryModel
import org.linphone.ui.main.chat.model.ChatMessageModel
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.fragment.GenericFragment
import org.linphone.utils.AppUtils
import org.linphone.utils.Event
import org.linphone.utils.FileUtils
import org.linphone.utils.LinphoneUtils
import org.linphone.utils.addCharacterAtPosition
import org.linphone.utils.hideKeyboard
@ -98,8 +100,18 @@ class ConversationFragment : GenericFragment() {
ActivityResultContracts.PickMultipleVisualMedia()
) { list ->
if (!list.isNullOrEmpty()) {
for (file in list) {
Log.i("$TAG Picked file [$file]")
for (uri in list) {
lifecycleScope.launch {
withContext(Dispatchers.IO) {
val path = FileUtils.getFilePath(requireContext(), uri, false)
Log.i("$TAG Picked file [$uri] matching path is [$path]")
if (path != null) {
withContext(Dispatchers.Main) {
viewModel.addAttachment(path)
}
}
}
}
}
} else {
Log.w("$TAG No file picked")
@ -288,6 +300,12 @@ class ConversationFragment : GenericFragment() {
}
}
viewModel.emojiToAddEvent.observe(viewLifecycleOwner) {
it.consume { emoji ->
binding.sendArea.messageToSend.addCharacterAtPosition(emoji)
}
}
viewModel.participantUsernameToAddEvent.observe(viewLifecycleOwner) {
it.consume { username ->
Log.i("$TAG Adding username [$username] after '@'")
@ -351,6 +369,33 @@ class ConversationFragment : GenericFragment() {
}
}
sharedViewModel.richContentUri.observe(
viewLifecycleOwner
) {
it.consume { uri ->
Log.i("$TAG Found rich content URI: $uri")
lifecycleScope.launch {
withContext(Dispatchers.IO) {
val path = FileUtils.getFilePath(requireContext(), uri, false)
Log.i("$TAG Rich content URI [$uri] matching path is [$path]")
if (path != null) {
withContext(Dispatchers.Main) {
viewModel.addAttachment(path)
}
}
}
}
}
}
binding.sendArea.messageToSend.setControlEnterListener(object :
RichEditText.RichEditTextSendListener {
override fun onControlEnterPressedAndReleased() {
Log.i("$TAG Detected left control + enter key presses, sending message")
viewModel.sendMessage()
}
})
binding.root.setKeyboardInsetListener { keyboardVisible ->
if (keyboardVisible) {
viewModel.isEmojiPickerOpen.value = false

View file

@ -1,19 +1,33 @@
package org.linphone.ui.main.chat.model
import androidx.annotation.WorkerThread
import androidx.annotation.AnyThread
import androidx.annotation.UiThread
import androidx.lifecycle.MutableLiveData
import org.linphone.core.tools.Log
import org.linphone.utils.FileUtils
class FileModel @WorkerThread constructor(
class FileModel @AnyThread constructor(
val file: String,
private val onClicked: ((file: String) -> Unit)? = null
) {
companion object {
private const val TAG = "[File Model]"
}
val path = MutableLiveData<String>()
init {
path.postValue(file)
}
@UiThread
fun onClick() {
onClicked?.invoke(file)
}
@AnyThread
suspend fun deleteFile() {
Log.i("$TAG Deleting file [$file]")
FileUtils.deleteFile(file)
}
}

View file

@ -0,0 +1,53 @@
/*
* 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.receiver
import android.content.ClipData
import android.net.Uri
import android.view.View
import androidx.core.util.component1
import androidx.core.util.component2
import androidx.core.view.ContentInfoCompat
import androidx.core.view.OnReceiveContentListener
import org.linphone.core.tools.Log
class RichContentReceiver(private val contentReceived: (uri: Uri) -> Unit) :
OnReceiveContentListener {
companion object {
private const val TAG = "[Rich Content Receiver]"
val MIME_TYPES = arrayOf("image/png", "image/gif", "image/jpeg")
}
override fun onReceiveContent(view: View, payload: ContentInfoCompat): ContentInfoCompat? {
val (uriContent, remaining) = payload.partition { item -> item.uri != null }
if (uriContent != null) {
val clip: ClipData = uriContent.clip
for (i in 0 until clip.itemCount) {
val uri: Uri = clip.getItemAt(i).uri
Log.i("$TAG Found URI: $uri")
contentReceived(uri)
}
}
// Return anything that your app didn't handle. This preserves the default platform
// behavior for text and anything else that you aren't implementing custom handling for.
return remaining
}
}

View file

@ -0,0 +1,101 @@
/*
* 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.view
import android.app.Activity
import android.content.Context
import android.util.AttributeSet
import android.view.KeyEvent
import androidx.appcompat.widget.AppCompatEditText
import androidx.core.view.ViewCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import org.linphone.core.tools.Log
import org.linphone.ui.main.chat.receiver.RichContentReceiver
import org.linphone.ui.main.viewmodel.SharedMainViewModel
import org.linphone.utils.Event
/**
* Allows for image input inside an EditText, usefull for keyboards with gif support for example.
*/
class RichEditText : AppCompatEditText {
companion object {
private const val TAG = "[Rich Edit Text]"
}
private var controlPressed = false
private var sendListener: RichEditTextSendListener? = null
constructor(context: Context) : super(context) {
initReceiveContentListener()
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
initReceiveContentListener()
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
initReceiveContentListener()
}
fun setControlEnterListener(listener: RichEditTextSendListener) {
sendListener = listener
}
private fun initReceiveContentListener() {
ViewCompat.setOnReceiveContentListener(
this,
RichContentReceiver.MIME_TYPES,
RichContentReceiver { uri ->
Log.i("$TAG Received URI: $uri")
val activity = context as Activity
val sharedViewModel = activity.run {
ViewModelProvider(activity as ViewModelStoreOwner)[SharedMainViewModel::class.java]
}
sharedViewModel.richContentUri.value = Event(uri)
}
)
setOnKeyListener { _, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_CTRL_LEFT) {
if (event.action == KeyEvent.ACTION_DOWN) {
controlPressed = true
} else if (event.action == KeyEvent.ACTION_UP) {
controlPressed = false
}
false
} else if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_UP && controlPressed) {
sendListener?.onControlEnterPressedAndReleased()
true
} else {
false
}
}
}
interface RichEditTextSendListener {
fun onControlEnterPressedAndReleased()
}
}

View file

@ -23,6 +23,8 @@ 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
@ -35,6 +37,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.FileModel
import org.linphone.ui.main.chat.model.ParticipantModel
import org.linphone.ui.main.contacts.model.ContactAvatarModel
import org.linphone.utils.AppUtils
@ -75,9 +78,9 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
val participants = MutableLiveData<ArrayList<ParticipantModel>>()
val participantUsernameToAddEvent: MutableLiveData<Event<String>> by lazy {
MutableLiveData<Event<String>>()
}
val isFileAttachmentsListOpen = MutableLiveData<Boolean>()
val attachments = MutableLiveData<ArrayList<FileModel>>()
val isReplying = MutableLiveData<Boolean>()
@ -107,6 +110,14 @@ 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
@ -210,6 +221,12 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
override fun onCleared() {
super.onCleared()
viewModelScope.launch {
for (file in attachments.value.orEmpty()) {
file.deleteFile()
}
}
coreContext.postOnCoreThread {
if (::chatRoom.isInitialized) {
chatRoom.removeListener(chatRoomListener)
@ -309,7 +326,7 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
@UiThread
fun insertEmoji(emoji: String) {
textToSend.value = "${textToSend.value.orEmpty()}$emoji"
emojiToAddEvent.value = Event(emoji)
}
@UiThread
@ -378,6 +395,61 @@ 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
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
}
}
@WorkerThread
private fun configureChatRoom() {
scrollingPosition = SCROLLING_POSITION_NOT_SET

View file

@ -19,6 +19,7 @@
*/
package org.linphone.ui.main.viewmodel
import android.net.Uri
import androidx.annotation.UiThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@ -106,11 +107,14 @@ class SharedMainViewModel @UiThread constructor() : ViewModel() {
MutableLiveData<Event<Boolean>>()
}
var displayedChatRoom: ChatRoom? = null
var displayedChatRoom: ChatRoom? = null // Prevents the need to go look for the chat room
val showConversationEvent: MutableLiveData<Event<Pair<String, String>>> by lazy {
MutableLiveData<Event<Pair<String, String>>>()
}
// When using keyboard to share gif or other, see RichContentReceiver & RichEditText classes
val richContentUri = MutableLiveData<Event<Uri>>()
/* Meetings related */
val showScheduleMeetingEvent: MutableLiveData<Event<Boolean>> by lazy {

View file

@ -156,6 +156,25 @@ class FileUtils {
return false
}
suspend fun deleteFile(filePath: String) {
withContext(Dispatchers.IO) {
val file = File(filePath)
if (file.exists()) {
try {
if (file.delete()) {
Log.i("$TAG Deleted $filePath")
} else {
Log.e("$TAG Can't delete $filePath")
}
} catch (e: Exception) {
Log.e("$TAG Can't delete $filePath, exception: $e")
}
} else {
Log.e("$TAG File $filePath doesn't exists")
}
}
}
@AnyThread
suspend fun dumpStringToFile(data: String, to: File): Boolean {
try {

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="org.linphone.ui.main.chat.viewmodel.ConversationViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white">
<View
android:id="@+id/separator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/gray_main2_300"
app:layout_constraintTop_toTopOf="parent"/>
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/files"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:alignItems="center"
app:flexWrap="wrap"
app:justifyContent="flex_start"
entries="@{viewModel.attachments}"
layout="@{@layout/chat_bubble_content_grid_cell}"
app:layout_constraintTop_toBottomOf="@id/separator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<ImageView
android:id="@+id/close"
android:onClick="@{() -> viewModel.closeFileAttachmentsList()}"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:src="@drawable/x"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/separator" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -222,31 +222,10 @@
android:textSize="12sp"
android:textColor="@color/gray_main2_400"
android:visibility="@{viewModel.composingLabel.length() == 0 ? View.GONE : View.VISIBLE}"
app:layout_constraintBottom_toTopOf="@id/participants"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="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>
<include

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="org.linphone.ui.main.chat.viewmodel.ConversationViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white">
<View
android:id="@+id/separator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/gray_main2_300"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.core.widget.NestedScrollView
android:id="@+id/participants"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintHeight_max="@dimen/chat_room_participants_list_max_height"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/separator">
<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>
<ImageView
android:id="@+id/close"
android:onClick="@{() -> viewModel.closeParticipantsList()}"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:src="@drawable/x"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/separator" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -24,10 +24,9 @@
<ImageView
android:id="@+id/cancel"
android:onClick="@{() -> viewModel.cancelReply()}"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:padding="15dp"
android:adjustViewBounds="true"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_marginEnd="10dp"
android:src="@drawable/x"
app:tint="@color/icon_color_selector"
app:layout_constraintEnd_toEndOf="parent"

View file

@ -24,43 +24,69 @@
layout="@layout/chat_conversation_reply_area"
viewModel="@{viewModel}"
android:visibility="@{viewModel.isReplying ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toTopOf="parent"/>
app:layout_constraintTop_toTopOf="parent" />
<include
android:id="@+id/attachments"
layout="@layout/chat_conversation_attachments_area"
viewModel="@{viewModel}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="@{viewModel.isFileAttachmentsListOpen ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/reply_area" />
<include
android:id="@+id/participants"
layout="@layout/chat_conversation_participants_area"
viewModel="@{viewModel}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="@{viewModel.isParticipantsListOpen ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/attachments" />
<androidx.emoji2.emojipicker.EmojiPickerView
android:id="@+id/emoji_picker"
android:layout_width="match_parent"
android:layout_height="@dimen/chat_room_emoji_picker_height"
android:background="@color/gray_100"
android:visibility="@{viewModel.isEmojiPickerOpen ? View.VISIBLE : View.GONE, default=gone}"
app:emojiPickedListener="@{(emoji) -> viewModel.insertEmoji(emoji.emoji)}"
app:layout_constraintTop_toBottomOf="@id/reply_area"/>
app:layout_constraintTop_toBottomOf="@id/attachments" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/top_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="emoji_picker, participants, attachments, reply_area" />
<ImageView
android:id="@+id/emoji_picker_toggle"
android:onClick="@{() -> viewModel.toggleEmojiPickerVisibility()}"
android:layout_width="40dp"
android:layout_height="0dp"
android:padding="8dp"
android:layout_marginStart="8dp"
android:onClick="@{() -> viewModel.toggleEmojiPickerVisibility()}"
android:padding="8dp"
android:src="@{viewModel.isEmojiPickerOpen ? @drawable/x : @drawable/smiley, default=@drawable/smiley}"
app:tint="@color/icon_color_selector"
app:layout_constraintTop_toTopOf="@id/message_area_background"
app:layout_constraintBottom_toBottomOf="@id/message_area_background"
app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/message_area_background"
app:tint="@color/icon_color_selector" />
<ImageView
android:id="@+id/attach_file"
android:onClick="@{openFilePickerClickListener}"
android:layout_width="40dp"
android:layout_height="0dp"
android:padding="8dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:onClick="@{openFilePickerClickListener}"
android:padding="8dp"
android:src="@drawable/paperclip"
app:tint="@color/icon_color_selector"
app:layout_constraintTop_toTopOf="@id/message_area_background"
app:layout_constraintBottom_toBottomOf="@id/message_area_background"
app:layout_constraintEnd_toStartOf="@id/message_area_background"
app:layout_constraintStart_toEndOf="@id/emoji_picker_toggle"
app:layout_constraintEnd_toStartOf="@id/message_area_background"/>
app:layout_constraintTop_toTopOf="@id/message_area_background"
app:tint="@color/icon_color_selector" />
<ImageView
android:id="@+id/message_area_background"
@ -68,32 +94,33 @@
android:layout_height="0dp"
android:layout_marginEnd="16dp"
android:src="@drawable/edit_text_background"
app:layout_constraintStart_toEndOf="@id/attach_file"
app:layout_constraintBottom_toBottomOf="@id/message_to_send"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/message_to_send"
app:layout_constraintBottom_toBottomOf="@id/message_to_send"/>
app:layout_constraintStart_toEndOf="@id/attach_file"
app:layout_constraintTop_toTopOf="@id/message_to_send" />
<androidx.appcompat.widget.AppCompatEditText
style="@style/default_text_style"
<org.linphone.ui.main.chat.view.RichEditText
android:id="@+id/message_to_send"
style="@style/default_text_style"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="5dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="5dp"
android:layout_marginBottom="16dp"
android:minHeight="48dp"
android:background="@color/transparent_color"
android:text="@={viewModel.textToSend}"
android:textSize="14sp"
android:textColorHint="@color/gray_main2_400"
android:maxLines="3"
android:hint="@string/conversation_text_field_hint"
android:inputType="text|textCapSentences|textAutoCorrect"
app:layout_constraintTop_toBottomOf="@id/emoji_picker"
app:layout_constraintStart_toStartOf="@id/message_area_background"
android:imeOptions="flagNoPersonalizedLearning"
android:inputType="textShortMessage|textMultiLine|textAutoComplete|textAutoCorrect|textCapSentences"
android:maxLines="3"
android:minHeight="48dp"
android:text="@={viewModel.textToSend}"
android:textColorHint="@color/gray_main2_400"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/send_barrier"
app:layout_constraintBottom_toBottomOf="parent"/>
app:layout_constraintStart_toStartOf="@id/message_area_background"
app:layout_constraintTop_toBottomOf="@id/top_barrier" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/send_barrier"
@ -109,24 +136,24 @@
android:layout_marginEnd="12dp"
android:src="@drawable/microphone"
android:visibility="gone"
app:tint="@color/icon_color_selector"
app:layout_constraintTop_toTopOf="@id/message_area_background"
app:layout_constraintBottom_toBottomOf="@id/message_area_background"
app:layout_constraintEnd_toEndOf="@id/message_area_background" />
app:layout_constraintEnd_toEndOf="@id/message_area_background"
app:layout_constraintTop_toTopOf="@id/message_area_background"
app:tint="@color/icon_color_selector" />
<ImageView
android:id="@+id/send_message"
android:onClick="@{() -> viewModel.sendMessage()}"
android:layout_width="40dp"
android:layout_height="0dp"
android:padding="8dp"
android:layout_marginEnd="4dp"
android:src="@drawable/paper_plane_tilt"
android:enabled="@{viewModel.textToSend.length() > 0}"
app:tint="@color/icon_primary_color_selector"
app:layout_constraintTop_toTopOf="@id/message_area_background"
android:onClick="@{() -> viewModel.sendMessage()}"
android:padding="8dp"
android:src="@drawable/paper_plane_tilt"
app:layout_constraintBottom_toBottomOf="@id/message_area_background"
app:layout_constraintEnd_toEndOf="@id/message_area_background" />
app:layout_constraintEnd_toEndOf="@id/message_area_background"
app:layout_constraintTop_toTopOf="@id/message_area_background"
app:tint="@color/icon_primary_color_selector" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -232,7 +232,7 @@
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:nestedScrollingEnabled="true"
app:layout_constraintHeight_max="300dp"
app:layout_constraintHeight_max="@dimen/chat_room_participants_list_max_height"
app:layout_constrainedHeight="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"

View file

@ -74,6 +74,7 @@
<dimen name="chat_bubble_images_rounded_corner_radius">5dp</dimen>
<dimen name="chat_bubble_start_margin_when_avatar_displayed">10dp</dimen>
<dimen name="chat_room_emoji_picker_height">290dp</dimen>
<dimen name="chat_room_emoji_picker_height">300dp</dimen>
<dimen name="chat_room_participants_list_max_height">300dp</dimen>
<dimen name="chat_bubble_emoji_picker_height">425dp</dimen>
</resources>