mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 19:38:08 +00:00
Reworked participants list for mentions a bit, added possibility to attach/remove files to/from sending area
This commit is contained in:
parent
713e048db9
commit
116ca3cbfe
14 changed files with 493 additions and 73 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
101
app/src/main/java/org/linphone/ui/main/chat/view/RichEditText.kt
Normal file
101
app/src/main/java/org/linphone/ui/main/chat/view/RichEditText.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
Loading…
Add table
Reference in a new issue