No longer use dialog for chat message long press

This commit is contained in:
Sylvain Berfini 2024-07-18 12:19:50 +02:00
parent a73e483478
commit 4c9bdec61e
8 changed files with 668 additions and 550 deletions

View file

@ -21,15 +21,9 @@ package org.linphone.ui.main.chat.fragment
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.app.Dialog
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.drawable.ColorDrawable
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.Editable
@ -40,18 +34,13 @@ import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.view.Window
import android.view.WindowManager
import android.widget.PopupWindow import android.widget.PopupWindow
import androidx.activity.result.PickVisualMediaRequest 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.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.doOnPreDraw import androidx.core.view.doOnPreDraw
import androidx.core.view.updatePadding
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -75,7 +64,6 @@ import org.linphone.R
import org.linphone.compatibility.Compatibility import org.linphone.compatibility.Compatibility
import org.linphone.core.ChatMessage import org.linphone.core.ChatMessage
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.databinding.ChatBubbleLongPressMenuBinding
import org.linphone.databinding.ChatConversationFragmentBinding import org.linphone.databinding.ChatConversationFragmentBinding
import org.linphone.databinding.ChatConversationPopupMenuBinding import org.linphone.databinding.ChatConversationPopupMenuBinding
import org.linphone.ui.GenericActivity import org.linphone.ui.GenericActivity
@ -88,6 +76,7 @@ import org.linphone.ui.main.chat.model.MessageDeliveryModel
import org.linphone.ui.main.chat.model.MessageModel import org.linphone.ui.main.chat.model.MessageModel
import org.linphone.ui.main.chat.model.MessageReactionsModel import org.linphone.ui.main.chat.model.MessageReactionsModel
import org.linphone.ui.main.chat.view.RichEditText import org.linphone.ui.main.chat.view.RichEditText
import org.linphone.ui.main.chat.viewmodel.ChatMessageLongPressViewModel
import org.linphone.ui.main.chat.viewmodel.ConversationViewModel 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.ConversationViewModel.Companion.SCROLLING_POSITION_NOT_SET
import org.linphone.ui.main.chat.viewmodel.SendMessageInConversationViewModel import org.linphone.ui.main.chat.viewmodel.SendMessageInConversationViewModel
@ -119,12 +108,12 @@ open class ConversationFragment : SlidingPaneChildFragment() {
protected lateinit var sendMessageViewModel: SendMessageInConversationViewModel protected lateinit var sendMessageViewModel: SendMessageInConversationViewModel
protected lateinit var messageLongPressViewModel: ChatMessageLongPressViewModel
private lateinit var adapter: ConversationEventAdapter private lateinit var adapter: ConversationEventAdapter
private lateinit var bottomSheetAdapter: MessageBottomSheetAdapter private lateinit var bottomSheetAdapter: MessageBottomSheetAdapter
private var messageLongPressDialog: Dialog? = null
private val args: ConversationFragmentArgs by navArgs() private val args: ConversationFragmentArgs by navArgs()
private var bottomSheetDialog: BottomSheetDialogFragment? = null private var bottomSheetDialog: BottomSheetDialogFragment? = null
@ -364,6 +353,11 @@ open class ConversationFragment : SlidingPaneChildFragment() {
binding.sendMessageViewModel = sendMessageViewModel binding.sendMessageViewModel = sendMessageViewModel
observeToastEvents(sendMessageViewModel) observeToastEvents(sendMessageViewModel)
messageLongPressViewModel = ViewModelProvider(this)[ChatMessageLongPressViewModel::class.java]
binding.messageLongPressViewModel = messageLongPressViewModel
observeToastEvents(messageLongPressViewModel)
messageLongPressViewModel.setupEmojiPicker(binding.longPressMenu.emojiPickerBottomSheet)
binding.setBackClickListener { binding.setBackClickListener {
goBack() goBack()
} }
@ -617,7 +611,7 @@ open class ConversationFragment : SlidingPaneChildFragment() {
viewModel.fileToDisplayEvent.observe(viewLifecycleOwner) { viewModel.fileToDisplayEvent.observe(viewLifecycleOwner) {
it.consume { model -> it.consume { model ->
if (messageLongPressDialog != null) return@consume if (messageLongPressViewModel.visible.value == true) return@consume
Log.i("$TAG User clicked on file [${model.path}], let's display it in file viewer") Log.i("$TAG User clicked on file [${model.path}], let's display it in file viewer")
goToFileViewer(model) goToFileViewer(model)
} }
@ -625,7 +619,7 @@ open class ConversationFragment : SlidingPaneChildFragment() {
viewModel.conferenceToJoinEvent.observe(viewLifecycleOwner) { viewModel.conferenceToJoinEvent.observe(viewLifecycleOwner) {
it.consume { conferenceUri -> it.consume { conferenceUri ->
if (messageLongPressDialog != null) return@consume if (messageLongPressViewModel.visible.value == true) return@consume
Log.i("$TAG Requesting to go to waiting room for conference URI [$conferenceUri]") Log.i("$TAG Requesting to go to waiting room for conference URI [$conferenceUri]")
sharedViewModel.goToMeetingWaitingRoomEvent.value = Event(conferenceUri) sharedViewModel.goToMeetingWaitingRoomEvent.value = Event(conferenceUri)
} }
@ -633,7 +627,7 @@ open class ConversationFragment : SlidingPaneChildFragment() {
viewModel.openWebBrowserEvent.observe(viewLifecycleOwner) { viewModel.openWebBrowserEvent.observe(viewLifecycleOwner) {
it.consume { url -> it.consume { url ->
if (messageLongPressDialog != null) return@consume if (messageLongPressViewModel.visible.value == true) return@consume
Log.i("$TAG Requesting to open web browser on page [$url]") Log.i("$TAG Requesting to open web browser on page [$url]")
try { try {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
@ -648,7 +642,7 @@ open class ConversationFragment : SlidingPaneChildFragment() {
viewModel.contactToDisplayEvent.observe(viewLifecycleOwner) { viewModel.contactToDisplayEvent.observe(viewLifecycleOwner) {
it.consume { friendRefKey -> it.consume { friendRefKey ->
if (messageLongPressDialog != null) return@consume if (messageLongPressViewModel.visible.value == true) return@consume
Log.i("$TAG Navigating to contact with ref key [$friendRefKey]") Log.i("$TAG Navigating to contact with ref key [$friendRefKey]")
sharedViewModel.navigateToContactsEvent.value = Event(true) sharedViewModel.navigateToContactsEvent.value = Event(true)
sharedViewModel.showContactEvent.value = Event(friendRefKey) sharedViewModel.showContactEvent.value = Event(friendRefKey)
@ -671,6 +665,49 @@ open class ConversationFragment : SlidingPaneChildFragment() {
} }
} }
messageLongPressViewModel.replyToMessageEvent.observe(viewLifecycleOwner) {
it.consume {
val model = messageLongPressViewModel.messageModel.value
if (model != null) {
sendMessageViewModel.replyToMessage(model)
// Open keyboard & focus edit text
binding.sendArea.messageToSend.showKeyboard()
}
}
}
messageLongPressViewModel.deleteMessageEvent.observe(viewLifecycleOwner) {
it.consume {
val model = messageLongPressViewModel.messageModel.value
if (model != null) {
viewModel.deleteChatMessage(model)
}
}
}
messageLongPressViewModel.forwardMessageEvent.observe(viewLifecycleOwner) {
it.consume {
val model = messageLongPressViewModel.messageModel.value
if (model != null) {
// Remove observer before setting the message to forward
// as we don't want to forward it in this chat room
sharedViewModel.messageToForwardEvent.removeObservers(viewLifecycleOwner)
sharedViewModel.messageToForwardEvent.postValue(Event(model))
if (findNavController().currentDestination?.id == R.id.conversationFragment) {
val action = ConversationFragmentDirections.actionConversationFragmentToConversationForwardMessageFragment()
findNavController().navigate(action)
}
}
}
}
messageLongPressViewModel.onDismissedEvent.observe(viewLifecycleOwner) {
it.consume {
Compatibility.removeBlurRenderEffect(binding.coordinatorLayout)
}
}
sharedViewModel.richContentUri.observe( sharedViewModel.richContentUri.observe(
viewLifecycleOwner viewLifecycleOwner
) { ) {
@ -1008,116 +1045,16 @@ open class ConversationFragment : SlidingPaneChildFragment() {
popupWindow.showAsDropDown(view, 0, 0, Gravity.BOTTOM) popupWindow.showAsDropDown(view, 0, 0, Gravity.BOTTOM)
} }
private fun dismissDialog() { @UiThread
messageLongPressDialog?.dismiss()
messageLongPressDialog = null
}
private fun showChatMessageLongPressMenu(chatMessageModel: MessageModel) { private fun showChatMessageLongPressMenu(chatMessageModel: MessageModel) {
Compatibility.setBlurRenderEffect(binding.root) Compatibility.setBlurRenderEffect(binding.coordinatorLayout)
messageLongPressViewModel.setMessage(chatMessageModel)
val layout: ChatBubbleLongPressMenuBinding = DataBindingUtil.inflate(
LayoutInflater.from(context),
R.layout.chat_bubble_long_press_menu,
null,
false
)
val emojiSheetBehavior = BottomSheetBehavior.from(layout.emojiPickerBottomSheet.root)
emojiSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiSheetBehavior.skipCollapsed = true
layout.root.setOnClickListener {
dismissDialog()
}
layout.setDeleteClickListener {
Log.i("$TAG Deleting message")
viewModel.deleteChatMessage(chatMessageModel)
dismissDialog()
}
layout.setCopyClickListener {
Log.i("$TAG Copying message text into clipboard")
val text = chatMessageModel.text.value?.toString()
val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val label = "Message"
clipboard.setPrimaryClip(ClipData.newPlainText(label, text))
dismissDialog()
}
layout.setPickEmojiClickListener {
Log.i("$TAG Opening emoji-picker for reaction")
emojiSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
}
layout.setResendClickListener {
Log.i("$TAG Re-sending message in error state")
chatMessageModel.resend()
dismissDialog()
}
layout.setForwardClickListener {
Log.i("$TAG Forwarding message")
// Remove observer before setting the message to forward
// as we don't want to forward it in this chat room
sharedViewModel.messageToForwardEvent.removeObservers(viewLifecycleOwner)
sharedViewModel.messageToForwardEvent.postValue(Event(chatMessageModel))
dismissDialog()
if (findNavController().currentDestination?.id == R.id.conversationFragment) {
val action = ConversationFragmentDirections.actionConversationFragmentToConversationForwardMessageFragment()
findNavController().navigate(action)
}
}
layout.setReplyClickListener {
Log.i("$TAG Updating sending area to reply to selected message")
sendMessageViewModel.replyToMessage(chatMessageModel)
dismissDialog()
// Open keyboard & focus edit text
binding.sendArea.messageToSend.showKeyboard()
}
layout.model = chatMessageModel
chatMessageModel.dismissLongPressMenuEvent.observe(viewLifecycleOwner) { chatMessageModel.dismissLongPressMenuEvent.observe(viewLifecycleOwner) {
dismissDialog() it.consume {
} messageLongPressViewModel.dismiss()
val dialog = Dialog(requireContext(), R.style.Theme_LinphoneDialog)
dialog.apply {
setOnDismissListener {
Compatibility.removeBlurRenderEffect(binding.root)
}
requestWindowFeature(Window.FEATURE_NO_TITLE)
window?.apply {
setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT
)
setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
val d: Drawable = ColorDrawable(
requireContext().getColor(R.color.grey_300)
)
d.alpha = 102
setBackgroundDrawable(d)
}
setContentView(layout.root)
ViewCompat.setOnApplyWindowInsetsListener(layout.root) { v, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
v.updatePadding(0, 0, 0, insets.bottom)
WindowInsetsCompat.CONSUMED
} }
} }
messageLongPressViewModel.visible.value = true
dialog.show()
messageLongPressDialog = dialog
} }
@UiThread @UiThread

View file

@ -0,0 +1,146 @@
/*
* Copyright (c) 2010-2024 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 android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.view.View
import androidx.annotation.UiThread
import androidx.lifecycle.MutableLiveData
import com.google.android.material.bottomsheet.BottomSheetBehavior
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.tools.Log
import org.linphone.databinding.ChatBubbleEmojiPickerBottomSheetBinding
import org.linphone.ui.GenericViewModel
import org.linphone.ui.main.chat.model.MessageModel
import org.linphone.utils.Event
class ChatMessageLongPressViewModel : GenericViewModel() {
companion object {
const val TAG = "[Chat Message LongPress ViewModel]"
}
val visible = MutableLiveData<Boolean>()
val hideForward = MutableLiveData<Boolean>()
val horizontalBias = MutableLiveData<Float>()
val isChatRoomReadOnly = MutableLiveData<Boolean>()
val messageModel = MutableLiveData<MessageModel>()
val isMessageOutgoing = MutableLiveData<Boolean>()
val isMessageInError = MutableLiveData<Boolean>()
val replyToMessageEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
val forwardMessageEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
val deleteMessageEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
val onDismissedEvent = MutableLiveData<Event<Boolean>>()
private lateinit var emojiBottomSheet: ChatBubbleEmojiPickerBottomSheetBinding
private lateinit var emojiBottomSheetBehavior: BottomSheetBehavior<View>
init {
visible.value = false
}
@UiThread
fun setupEmojiPicker(bottomSheet: ChatBubbleEmojiPickerBottomSheetBinding) {
emojiBottomSheetBehavior = BottomSheetBehavior.from(bottomSheet.root)
emojiBottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBottomSheetBehavior.skipCollapsed = true
}
@UiThread
fun setMessage(model: MessageModel) {
isChatRoomReadOnly.value = model.chatRoomIsReadOnly
isMessageOutgoing.value = model.isOutgoing
isMessageInError.value = model.isInError
horizontalBias.value = if (model.isOutgoing) 1f else 0f
messageModel.value = model
emojiBottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}
@UiThread
fun dismiss() {
onDismissedEvent.value = Event(true)
emojiBottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
visible.value = false
}
@UiThread
fun resend() {
Log.i("$TAG Re-sending message in error state")
messageModel.value?.resend()
dismiss()
}
@UiThread
fun reply() {
Log.i("$TAG Replying to message")
replyToMessageEvent.value = Event(true)
dismiss()
}
@UiThread
fun copyClickListener() {
Log.i("$TAG Copying message text into clipboard")
val text = messageModel.value?.text?.value?.toString()
val clipboard = coreContext.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val label = "Message"
clipboard.setPrimaryClip(ClipData.newPlainText(label, text))
dismiss()
}
@UiThread
fun forwardClickListener() {
Log.i("$TAG Forwarding message")
forwardMessageEvent.value = Event(true)
dismiss()
}
@UiThread
fun deleteClickListener() {
Log.i("$TAG Deleting message")
deleteMessageEvent.value = Event(true)
dismiss()
}
@UiThread
fun pickEmoji() {
Log.i("$TAG Opening emoji-picker for reaction")
emojiBottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
}
}

View file

@ -22,6 +22,12 @@
<variable <variable
name="model" name="model"
type="org.linphone.ui.main.chat.model.MessageModel" /> type="org.linphone.ui.main.chat.model.MessageModel" />
<variable
name="inflatedVisibility"
type="Integer" />
<variable
name="skipGroupMargins"
type="Boolean" />
</data> </data>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@ -30,8 +36,10 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginEnd="32dp" android:layout_marginEnd="32dp"
android:layout_marginBottom="@{model.groupedWithNextMessage ? @dimen/chat_bubble_grouped_bottom_margin : @dimen/chat_bubble_bottom_margin, default=@dimen/chat_bubble_bottom_margin}" android:layout_marginBottom="@{skipGroupMargins || model.groupedWithNextMessage ? @dimen/chat_bubble_grouped_bottom_margin : @dimen/chat_bubble_bottom_margin, default=@dimen/chat_bubble_bottom_margin}"
android:layout_marginTop="@{model.groupedWithPreviousMessage ? @dimen/chat_bubble_grouped_top_margin : @dimen/chat_bubble_top_margin, default=@dimen/chat_bubble_top_margin}"> android:layout_marginTop="@{skipGroupMargins || model.groupedWithPreviousMessage ? @dimen/chat_bubble_grouped_top_margin : @dimen/chat_bubble_top_margin, default=@dimen/chat_bubble_top_margin}"
android:visibility="@{inflatedVisibility == View.VISIBLE ? View.VISIBLE : View.GONE}"
inflatedLifecycleOwner="@{true}">
<include <include
android:id="@+id/avatar" android:id="@+id/avatar"

View file

@ -6,186 +6,171 @@
<data> <data>
<import type="android.view.View" /> <import type="android.view.View" />
<variable <variable
name="resendClickListener" name="viewModel"
type="View.OnClickListener" /> type="org.linphone.ui.main.chat.viewmodel.ChatMessageLongPressViewModel" />
<variable
name="replyClickListener"
type="View.OnClickListener" />
<variable
name="copyClickListener"
type="View.OnClickListener" />
<variable
name="forwardClickListener"
type="View.OnClickListener" />
<variable
name="deleteClickListener"
type="View.OnClickListener" />
<variable
name="pickEmojiClickListener"
type="View.OnClickListener" />
<variable
name="model"
type="org.linphone.ui.main.chat.model.MessageModel" />
<variable
name="hideForward"
type="Boolean" />
</data> </data>
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:onClick="@{() -> viewModel.dismiss()}">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraint_layout" android:id="@+id/constraint_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:background="@color/gray_300_alpha_40">
<androidx.constraintlayout.widget.Barrier
android:id="@+id/bubble_top_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="top"
app:constraint_referenced_ids="incoming_bubble, outgoing_bubble" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/bottom_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="top"
app:constraint_referenced_ids="action_reply, action_resend" />
<ViewStub
android:id="@+id/outgoing_bubble"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="12dp"
android:layout="@layout/chat_bubble_outgoing"
android:visibility="@{model.outgoing ? View.VISIBLE : View.GONE, default=gone}"
bind:inflatedVisibility="@{model.outgoing ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/emojis"
app:layout_constraintBottom_toTopOf="@id/bottom_barrier"
app:layout_constraintHeight_max="@dimen/chat_bubble_max_height_long_press"
bind:model="@{model}"/>
<ViewStub
android:id="@+id/incoming_bubble"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="12dp"
android:layout="@layout/chat_bubble_incoming"
android:visibility="@{model.outgoing ? View.GONE : View.VISIBLE}"
bind:inflatedVisibility="@{model.outgoing ? View.GONE : View.VISIBLE}"
app:layout_constraintTop_toBottomOf="@id/emojis"
app:layout_constraintBottom_toBottomOf="@id/bottom_barrier"
app:layout_constraintHeight_max="@dimen/chat_bubble_max_height_long_press"
bind:model="@{model}"/>
<include <include
android:id="@+id/emojis" android:id="@+id/emojis"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="12dp" android:layout_marginBottom="5dp"
android:visibility="@{model.chatRoomIsReadOnly ? View.GONE : View.VISIBLE}" android:visibility="@{viewModel.isChatRoomReadOnly ? View.GONE : View.VISIBLE}"
layout="@layout/chat_emoji_reaction_picker" layout="@layout/chat_emoji_reaction_picker"
bind:model="@{model}" bind:model="@{viewModel.messageModel}"
bind:pickEmojiClickListener="@{pickEmojiClickListener}" bind:pickEmojiClickListener="@{() -> viewModel.pickEmoji()}"
app:layout_constraintVertical_bias="1" app:layout_constraintHorizontal_bias="@{viewModel.horizontalBias, default=0}"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintWidth_max="@dimen/dialog_max_width"
app:layout_constraintBottom_toTopOf="@id/bubble_top_barrier" app:layout_constraintBottom_toTopOf="@id/bubbles"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintEnd_toEndOf="parent"/>
<View <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/actions_background" android:id="@+id/bubbles"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?attr/color_main2_200"
app:layout_constraintTop_toTopOf="@id/bottom_barrier"
app:layout_constraintBottom_toBottomOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/context_menu_action_label_style"
android:id="@+id/action_resend"
android:onClick="@{resendClickListener}"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="1dp" android:layout_marginStart="16dp"
android:text="@string/menu_resend_chat_message" android:layout_marginEnd="16dp"
android:background="@drawable/menu_item_background" android:layout_marginBottom="5dp"
android:drawableStart="@drawable/paper_plane_right"
android:visibility="@{model.isInError &amp;&amp; model.isOutgoing ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintBottom_toTopOf="@id/action_reply"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/actions">
<androidx.appcompat.widget.AppCompatTextView <ViewStub
style="@style/context_menu_action_label_style" android:id="@+id/outgoing_bubble"
android:id="@+id/action_reply" android:layout_width="match_parent"
android:onClick="@{replyClickListener}" android:layout_height="wrap_content"
android:layout="@layout/chat_bubble_outgoing"
android:visibility="@{viewModel.isMessageOutgoing ? View.VISIBLE : View.GONE, default=gone}"
bind:inflatedVisibility="@{viewModel.isMessageOutgoing ? View.VISIBLE : View.GONE, default=gone}"
bind:model="@{viewModel.messageModel}"
bind:skipGroupMargins="@{true}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_max="@dimen/chat_bubble_max_height_long_press"/>
<ViewStub
android:id="@+id/incoming_bubble"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/chat_bubble_incoming"
android:visibility="@{viewModel.isMessageOutgoing ? View.GONE : View.VISIBLE}"
bind:inflatedVisibility="@{viewModel.isMessageOutgoing ? View.GONE : View.VISIBLE}"
bind:model="@{viewModel.messageModel}"
bind:skipGroupMargins="@{true}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_max="@dimen/chat_bubble_max_height_long_press"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/actions"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="1dp" android:orientation="vertical"
android:text="@string/menu_reply_to_chat_message" android:layout_marginStart="16dp"
android:visibility="@{model.chatRoomIsReadOnly ? View.GONE : View.VISIBLE}" android:layout_marginEnd="16dp"
android:background="@drawable/menu_item_background" android:layout_marginBottom="60dp"
android:drawableStart="@drawable/reply" android:background="@drawable/shape_squircle_white_background"
app:layout_constraintBottom_toTopOf="@id/action_copy" app:layout_constraintWidth_max="250dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintHorizontal_bias="@{viewModel.horizontalBias, default=0}"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/context_menu_action_label_style"
android:id="@+id/action_copy"
android:onClick="@{copyClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="1dp"
android:text="@string/menu_copy_chat_message"
android:background="@drawable/menu_item_background"
android:drawableStart="@drawable/copy"
app:layout_constraintBottom_toTopOf="@id/action_forward"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/context_menu_action_label_style"
android:id="@+id/action_forward"
android:onClick="@{forwardClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="1dp"
android:text="@string/menu_forward_chat_message"
android:background="@drawable/menu_item_background"
android:drawableStart="@drawable/forward"
android:visibility="@{hideForward ? View.GONE : View.VISIBLE}"
app:layout_constraintBottom_toTopOf="@id/action_delete"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/context_menu_danger_action_label_style"
android:id="@+id/action_delete"
android:onClick="@{deleteClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="1dp"
android:text="@string/menu_delete_selected_item"
android:background="@drawable/menu_item_background"
android:drawableStart="@drawable/trash_simple"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintEnd_toEndOf="parent">
<androidx.appcompat.widget.AppCompatTextView
style="@style/popup_menu_action_label_style"
android:id="@+id/action_resend"
android:onClick="@{() -> viewModel.resend()}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/menu_resend_chat_message"
android:drawableEnd="@drawable/paper_plane_right"
android:visibility="@{viewModel.isMessageInError &amp;&amp; viewModel.isMessageOutgoing ? View.VISIBLE : View.GONE, default=gone}" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/color_main2_200"
android:visibility="@{viewModel.isMessageInError &amp;&amp; viewModel.isMessageOutgoing ? View.VISIBLE : View.GONE, default=gone}"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/popup_menu_action_label_style"
android:id="@+id/action_reply"
android:onClick="@{() -> viewModel.reply()}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/menu_reply_to_chat_message"
android:visibility="@{viewModel.isChatRoomReadOnly ? View.GONE : View.VISIBLE}"
android:drawableEnd="@drawable/reply" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/color_main2_200"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/popup_menu_action_label_style"
android:id="@+id/action_copy"
android:onClick="@{() -> viewModel.copyClickListener()}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/menu_copy_chat_message"
android:drawableEnd="@drawable/copy" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/color_main2_200"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/popup_menu_action_label_style"
android:id="@+id/action_forward"
android:onClick="@{() -> viewModel.forwardClickListener()}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/menu_forward_chat_message"
android:drawableEnd="@drawable/forward"
android:visibility="@{viewModel.hideForward ? View.GONE : View.VISIBLE}" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/color_main2_200"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/popup_menu_danger_action_label_style"
android:id="@+id/action_delete"
android:onClick="@{() -> viewModel.deleteClickListener()}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/menu_delete_selected_item"
android:drawableEnd="@drawable/trash_simple" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<include <include
android:id="@+id/emoji_picker_bottom_sheet" android:id="@+id/emoji_picker_bottom_sheet"
layout="@layout/chat_bubble_emoji_picker_bottom_sheet" layout="@layout/chat_bubble_emoji_picker_bottom_sheet"
bind:model="@{model}"/> bind:model="@{viewModel.messageModel}"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -22,6 +22,9 @@
<variable <variable
name="model" name="model"
type="org.linphone.ui.main.chat.model.MessageModel" /> type="org.linphone.ui.main.chat.model.MessageModel" />
<variable
name="inflatedVisibility"
type="Integer" />
</data> </data>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@ -31,7 +34,9 @@
android:layout_marginStart="32dp" android:layout_marginStart="32dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginBottom="@{model.groupedWithNextMessage ? @dimen/chat_bubble_grouped_bottom_margin : @dimen/chat_bubble_bottom_margin, default=@dimen/chat_bubble_bottom_margin}" android:layout_marginBottom="@{model.groupedWithNextMessage ? @dimen/chat_bubble_grouped_bottom_margin : @dimen/chat_bubble_bottom_margin, default=@dimen/chat_bubble_bottom_margin}"
android:layout_marginTop="@{model.groupedWithPreviousMessage ? @dimen/chat_bubble_grouped_top_margin : @dimen/chat_bubble_top_margin, default=@dimen/chat_bubble_top_margin}"> android:layout_marginTop="@{model.groupedWithPreviousMessage ? @dimen/chat_bubble_grouped_top_margin : @dimen/chat_bubble_top_margin, default=@dimen/chat_bubble_top_margin}"
android:visibility="@{inflatedVisibility == View.VISIBLE ? View.VISIBLE : View.GONE}"
inflatedLifecycleOwner="@{true}">
<androidx.constraintlayout.widget.Group <androidx.constraintlayout.widget.Group
android:id="@+id/reply_group" android:id="@+id/reply_group"

View file

@ -35,301 +35,317 @@
<variable <variable
name="sendMessageViewModel" name="sendMessageViewModel"
type="org.linphone.ui.main.chat.viewmodel.SendMessageInConversationViewModel" /> type="org.linphone.ui.main.chat.viewmodel.SendMessageInConversationViewModel" />
<variable
name="messageLongPressViewModel"
type="org.linphone.ui.main.chat.viewmodel.ChatMessageLongPressViewModel" />
</data> </data>
<androidx.coordinatorlayout.widget.CoordinatorLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinator_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:background="?attr/color_main2_000">
<androidx.constraintlayout.widget.Group <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
app:constraint_referenced_ids="avatar, title, show_menu" android:background="?attr/color_main2_000">
android:visibility="@{viewModel.searchBarVisible ? View.GONE : View.VISIBLE}" />
<androidx.constraintlayout.widget.Group <androidx.constraintlayout.widget.Group
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:constraint_referenced_ids="cancel_search, search, clear_field" app:constraint_referenced_ids="avatar, title, show_menu"
android:visibility="@{viewModel.searchBarVisible ? View.VISIBLE : View.GONE, default=gone}" /> android:visibility="@{viewModel.searchBarVisible ? View.GONE : View.VISIBLE}" />
<ImageView <androidx.constraintlayout.widget.Group
android:id="@+id/back" android:layout_width="wrap_content"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="@dimen/top_bar_height" app:constraint_referenced_ids="cancel_search, search, clear_field"
android:padding="15dp" android:visibility="@{viewModel.searchBarVisible ? View.VISIBLE : View.GONE, default=gone}" />
android:adjustViewBounds="true"
android:onClick="@{backClickListener}"
android:visibility="@{viewModel.isInCallConversation || viewModel.showBackButton &amp;&amp; !viewModel.searchBarVisible ? View.VISIBLE : View.GONE}"
android:src="@drawable/caret_left"
android:contentDescription="@string/content_description_go_back_icon"
app:tint="?attr/color_main1_500"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<include <ImageView
android:id="@+id/avatar" android:id="@+id/back"
android:onClick="@{goToInfoClickListener}" android:layout_width="wrap_content"
android:layout_width="wrap_content" android:layout_height="@dimen/top_bar_height"
android:layout_height="wrap_content" android:padding="15dp"
android:layout_marginTop="5dp" android:adjustViewBounds="true"
android:layout_marginBottom="5dp" android:onClick="@{backClickListener}"
android:layout_marginStart="5dp" android:visibility="@{viewModel.isInCallConversation || viewModel.showBackButton &amp;&amp; !viewModel.searchBarVisible ? View.VISIBLE : View.GONE}"
layout="@layout/contact_avatar" android:src="@drawable/caret_left"
bind:model="@{viewModel.avatarModel}" android:contentDescription="@string/content_description_go_back_icon"
app:layout_constraintStart_toEndOf="@id/back" app:tint="?attr/color_main1_500"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView <include
style="@style/default_text_style" android:id="@+id/avatar"
android:id="@+id/title" android:onClick="@{goToInfoClickListener}"
android:onClick="@{goToInfoClickListener}" android:layout_width="wrap_content"
android:layout_width="0dp" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:layout_marginTop="5dp"
android:layout_marginStart="10dp" android:layout_marginBottom="5dp"
android:layout_marginEnd="5dp" android:layout_marginStart="5dp"
android:maxLines="1" layout="@layout/contact_avatar"
android:ellipsize="end" bind:model="@{viewModel.avatarModel}"
android:text="@{viewModel.isGroup ? viewModel.subject : viewModel.avatarModel.name, default=`John Doe`}" app:layout_constraintStart_toEndOf="@id/back"
android:textSize="16sp" app:layout_constraintTop_toTopOf="parent" />
android:textColor="?attr/color_main2_600"
android:gravity="center_vertical"
app:layout_constraintEnd_toStartOf="@id/start_call"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toTopOf="@id/avatar"
app:layout_constraintBottom_toTopOf="@id/subtitle_barrier"/>
<androidx.constraintlayout.widget.Barrier <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/subtitle_barrier" style="@style/default_text_style"
android:layout_width="wrap_content" android:id="@+id/title"
android:layout_height="wrap_content" android:onClick="@{goToInfoClickListener}"
app:barrierDirection="top" android:layout_width="0dp"
app:constraint_referenced_ids="muted, ephemeral_enabled" /> android:layout_height="wrap_content"
android:layout_marginStart="10dp"
<ImageView android:layout_marginEnd="5dp"
android:id="@+id/muted" android:maxLines="1"
android:layout_width="@dimen/small_icon_size" android:ellipsize="end"
android:layout_height="@dimen/small_icon_size" android:text="@{viewModel.isGroup ? viewModel.subject : viewModel.avatarModel.name, default=`John Doe`}"
android:layout_marginEnd="5dp"
android:src="@drawable/bell_simple_slash"
android:contentDescription="@string/content_description_chat_muted"
android:visibility="@{viewModel.isMuted ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toStartOf="@id/ephemeral_enabled"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintBottom_toBottomOf="@id/avatar"
app:tint="@color/main1_500"/>
<ImageView
android:id="@+id/ephemeral_enabled"
android:layout_width="@dimen/small_icon_size"
android:layout_height="@dimen/small_icon_size"
android:layout_marginEnd="5dp"
android:src="@drawable/clock_countdown"
android:contentDescription="@string/content_description_chat_ephemeral_enabled"
android:visibility="@{viewModel.ephemeralLifetime > 0L ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintStart_toEndOf="@id/muted"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintBottom_toBottomOf="@id/avatar"
app:tint="@color/main1_500"/>
<ImageView
android:id="@+id/show_menu"
android:onClick="@{showMenuClickListener}"
android:layout_width="wrap_content"
android:layout_height="@dimen/top_bar_height"
android:padding="15dp"
android:adjustViewBounds="true"
android:src="@drawable/dots_three_vertical"
android:contentDescription="@string/content_description_show_popup_menu"
android:visibility="@{viewModel.isInCallConversation ? View.GONE : View.VISIBLE}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:tint="?attr/color_main2_500"/>
<ImageView
android:id="@+id/start_call"
android:onClick="@{() -> viewModel.startCall()}"
android:layout_width="wrap_content"
android:layout_height="@dimen/top_bar_height"
android:padding="15dp"
android:src="@drawable/phone"
android:contentDescription="@string/content_description_call_start"
android:visibility="@{viewModel.isInCallConversation || viewModel.isReadOnly || viewModel.searchBarVisible ? View.GONE : View.VISIBLE}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/show_menu"
app:tint="?attr/color_main2_500" />
<ImageView
android:id="@+id/cancel_search"
android:onClick="@{() -> viewModel.closeSearchBar()}"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:padding="15dp"
android:src="@drawable/caret_left"
android:contentDescription="@string/content_description_cancel_filter"
app:layout_constraintBottom_toBottomOf="@id/search"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/search"
app:tint="?attr/color_main2_500" />
<com.google.android.material.textfield.TextInputLayout
style="?attr/textInputFilledStyle"
android:id="@+id/search"
android:layout_width="0dp"
android:layout_height="@dimen/top_bar_height"
android:gravity="center_vertical"
android:textColorHint="?attr/color_main2_400"
app:hintEnabled="false"
app:hintAnimationEnabled="false"
app:hintTextColor="?attr/color_main2_400"
app:boxStrokeWidth="0dp"
app:boxStrokeWidthFocused="0dp"
app:layout_constraintEnd_toStartOf="@id/clear_field"
app:layout_constraintStart_toEndOf="@id/cancel_search"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textCursorDrawable="@null"
android:textSize="16sp" android:textSize="16sp"
android:inputType="text" android:textColor="?attr/color_main2_600"
android:paddingVertical="1dp" android:gravity="center_vertical"
android:text="@={viewModel.searchFilter}" app:layout_constraintEnd_toStartOf="@id/start_call"
android:background="@android:color/transparent" /> app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toTopOf="@id/avatar"
app:layout_constraintBottom_toTopOf="@id/subtitle_barrier"/>
</com.google.android.material.textfield.TextInputLayout> <androidx.constraintlayout.widget.Barrier
android:id="@+id/subtitle_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="top"
app:constraint_referenced_ids="muted, ephemeral_enabled" />
<ImageView <ImageView
android:id="@+id/clear_field" android:id="@+id/muted"
android:onClick="@{() -> viewModel.clearFilter()}" android:layout_width="@dimen/small_icon_size"
android:enabled="@{viewModel.searchFilter.length() > 0}" android:layout_height="@dimen/small_icon_size"
android:layout_width="wrap_content" android:layout_marginEnd="5dp"
android:layout_height="wrap_content" android:src="@drawable/bell_simple_slash"
android:padding="6dp" android:contentDescription="@string/content_description_chat_muted"
android:layout_marginEnd="9dp" android:visibility="@{viewModel.isMuted ? View.VISIBLE : View.GONE, default=gone}"
android:src="@drawable/x" app:layout_constraintStart_toStartOf="@id/title"
android:contentDescription="@string/content_description_clear_filter" app:layout_constraintEnd_toStartOf="@id/ephemeral_enabled"
app:layout_constraintBottom_toBottomOf="@id/search" app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/avatar"
app:layout_constraintTop_toTopOf="@id/search" app:tint="@color/main1_500"/>
app:tint="?attr/color_main2_500" />
<androidx.recyclerview.widget.RecyclerView <ImageView
android:id="@+id/events_list" android:id="@+id/ephemeral_enabled"
android:layout_width="match_parent" android:layout_width="@dimen/small_icon_size"
android:layout_height="0dp" android:layout_height="@dimen/small_icon_size"
android:layout_marginTop="55dp" android:layout_marginEnd="5dp"
android:paddingBottom="5dp" android:src="@drawable/clock_countdown"
app:layout_constraintTop_toTopOf="parent" android:contentDescription="@string/content_description_chat_ephemeral_enabled"
app:layout_constraintBottom_toTopOf="@id/composing"/> android:visibility="@{viewModel.ephemeralLifetime > 0L ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintStart_toEndOf="@id/muted"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintBottom_toBottomOf="@id/avatar"
app:tint="@color/main1_500"/>
<ImageView
android:id="@+id/show_menu"
android:onClick="@{showMenuClickListener}"
android:layout_width="wrap_content"
android:layout_height="@dimen/top_bar_height"
android:padding="15dp"
android:adjustViewBounds="true"
android:src="@drawable/dots_three_vertical"
android:contentDescription="@string/content_description_show_popup_menu"
android:visibility="@{viewModel.isInCallConversation ? View.GONE : View.VISIBLE}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:tint="?attr/color_main2_500"/>
<ImageView
android:id="@+id/start_call"
android:onClick="@{() -> viewModel.startCall()}"
android:layout_width="wrap_content"
android:layout_height="@dimen/top_bar_height"
android:padding="15dp"
android:src="@drawable/phone"
android:contentDescription="@string/content_description_call_start"
android:visibility="@{viewModel.isInCallConversation || viewModel.isReadOnly || viewModel.searchBarVisible ? View.GONE : View.VISIBLE}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/show_menu"
app:tint="?attr/color_main2_500" />
<ImageView
android:id="@+id/cancel_search"
android:onClick="@{() -> viewModel.closeSearchBar()}"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:padding="15dp"
android:src="@drawable/caret_left"
android:contentDescription="@string/content_description_cancel_filter"
app:layout_constraintBottom_toBottomOf="@id/search"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/search"
app:tint="?attr/color_main2_500" />
<com.google.android.material.textfield.TextInputLayout
style="?attr/textInputFilledStyle"
android:id="@+id/search"
android:layout_width="0dp"
android:layout_height="@dimen/top_bar_height"
android:gravity="center_vertical"
android:textColorHint="?attr/color_main2_400"
app:hintEnabled="false"
app:hintAnimationEnabled="false"
app:hintTextColor="?attr/color_main2_400"
app:boxStrokeWidth="0dp"
app:boxStrokeWidthFocused="0dp"
app:layout_constraintEnd_toStartOf="@id/clear_field"
app:layout_constraintStart_toEndOf="@id/cancel_search"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textCursorDrawable="@null"
android:textSize="16sp"
android:inputType="text"
android:paddingVertical="1dp"
android:text="@={viewModel.searchFilter}"
android:background="@android:color/transparent" />
</com.google.android.material.textfield.TextInputLayout>
<ImageView
android:id="@+id/clear_field"
android:onClick="@{() -> viewModel.clearFilter()}"
android:enabled="@{viewModel.searchFilter.length() > 0}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="6dp"
android:layout_marginEnd="9dp"
android:src="@drawable/x"
android:contentDescription="@string/content_description_clear_filter"
app:layout_constraintBottom_toBottomOf="@id/search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/search"
app:tint="?attr/color_main2_500" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/events_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="55dp"
android:paddingBottom="5dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/composing"/>
<include
android:id="@+id/secured_event"
android:onClick="@{endToEndEncryptedEventClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="@{viewModel.isEmpty &amp;&amp; viewModel.isEndToEndEncrypted ? View.VISIBLE : View.GONE}"
layout="@layout/chat_conversation_secured_first_event"
app:layout_constraintTop_toTopOf="@id/events_list"
app:layout_constraintStart_toStartOf="@id/events_list"
app:layout_constraintEnd_toEndOf="@id/events_list" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_800"
android:id="@+id/no_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/list_filter_no_result_found"
android:textColor="?attr/color_main2_600"
android:textSize="16sp"
android:visibility="@{viewModel.noMatchingResultForFilter ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/search"
app:layout_constraintBottom_toTopOf="@id/send_area"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
android:id="@+id/composing"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:paddingBottom="5dp"
android:background="?attr/color_main2_000"
android:text="@{viewModel.composingLabel, default=`John Doe is composing...`}"
android:textSize="12sp"
android:textColor="?attr/color_main2_400"
android:visibility="@{viewModel.composingLabel.length() == 0 ? View.GONE : View.VISIBLE}"
app:layout_constraintBottom_toTopOf="@id/warning_disabled_not_secured"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<include
style="@style/default_text_style"
android:id="@+id/warning_disabled_not_secured"
android:onClick="@{warningConversationDisabledClickListener}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="@{viewModel.isDisabledBecauseNotSecured ? View.VISIBLE : View.GONE, default=gone}"
layout="@layout/chat_conversation_send_area_disabled_unsecured_warning"
app:layout_constraintBottom_toTopOf="@id/send_area"/>
<include
android:id="@+id/send_area"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="@{viewModel.isReadOnly || viewModel.isDisabledBecauseNotSecured ? View.GONE : View.VISIBLE}"
layout="@layout/chat_conversation_send_area"
app:layout_constraintBottom_toBottomOf="parent"
bind:openFilePickerClickListener="@{openFilePickerClickListener}"
bind:openCameraClickListener="@{openCameraClickListener}"
bind:viewModel="@{sendMessageViewModel}"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/scroll_to_bottom"
android:onClick="@{scrollToBottomClickListener}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:src="@drawable/caret_double_down"
android:contentDescription="@string/content_description_chat_scroll_to_bottom_or_first_unread"
android:visibility="@{viewModel.isUserScrollingUp ? View.VISIBLE : View.GONE}"
app:tint="?attr/color_on_main"
app:backgroundTint="?attr/color_main1_500"
app:shapeAppearanceOverlay="@style/rounded"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/send_area" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/unread_messages"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="-13dp"
android:layout_marginStart="-11dp"
android:gravity="center"
android:background="@drawable/shape_red_round"
android:text="@{String.valueOf(viewModel.unreadMessagesCount), default=`1`}"
android:textColor="?attr/color_on_main"
android:textSize="13sp"
android:visibility="@{viewModel.isUserScrollingUp &amp;&amp; viewModel.unreadMessagesCount > 0 ? View.VISIBLE : View.GONE}"
app:layout_constraintTop_toTopOf="@id/scroll_to_bottom"
app:layout_constraintStart_toEndOf="@id/scroll_to_bottom"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<include <include
android:id="@+id/secured_event" android:id="@+id/message_bottom_sheet"
android:onClick="@{endToEndEncryptedEventClickListener}" layout="@layout/chat_message_bottom_sheet" />
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="@{viewModel.isEmpty &amp;&amp; viewModel.isEndToEndEncrypted ? View.VISIBLE : View.GONE}"
layout="@layout/chat_conversation_secured_first_event"
app:layout_constraintTop_toTopOf="@id/events_list"
app:layout_constraintStart_toStartOf="@id/events_list"
app:layout_constraintEnd_toEndOf="@id/events_list" />
<androidx.appcompat.widget.AppCompatTextView </androidx.coordinatorlayout.widget.CoordinatorLayout>
style="@style/default_text_style_800"
android:id="@+id/no_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/list_filter_no_result_found"
android:textColor="?attr/color_main2_600"
android:textSize="16sp"
android:visibility="@{viewModel.noMatchingResultForFilter ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/search"
app:layout_constraintBottom_toTopOf="@id/send_area"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
android:id="@+id/composing"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:paddingBottom="5dp"
android:background="?attr/color_main2_000"
android:text="@{viewModel.composingLabel, default=`John Doe is composing...`}"
android:textSize="12sp"
android:textColor="?attr/color_main2_400"
android:visibility="@{viewModel.composingLabel.length() == 0 ? View.GONE : View.VISIBLE}"
app:layout_constraintBottom_toTopOf="@id/warning_disabled_not_secured"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<include
style="@style/default_text_style"
android:id="@+id/warning_disabled_not_secured"
android:onClick="@{warningConversationDisabledClickListener}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="@{viewModel.isDisabledBecauseNotSecured ? View.VISIBLE : View.GONE, default=gone}"
layout="@layout/chat_conversation_send_area_disabled_unsecured_warning"
app:layout_constraintBottom_toTopOf="@id/send_area"/>
<include
android:id="@+id/send_area"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="@{viewModel.isReadOnly || viewModel.isDisabledBecauseNotSecured ? View.GONE : View.VISIBLE}"
layout="@layout/chat_conversation_send_area"
app:layout_constraintBottom_toBottomOf="parent"
bind:openFilePickerClickListener="@{openFilePickerClickListener}"
bind:openCameraClickListener="@{openCameraClickListener}"
bind:viewModel="@{sendMessageViewModel}"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/scroll_to_bottom"
android:onClick="@{scrollToBottomClickListener}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:src="@drawable/caret_double_down"
android:contentDescription="@string/content_description_chat_scroll_to_bottom_or_first_unread"
android:visibility="@{viewModel.isUserScrollingUp ? View.VISIBLE : View.GONE}"
app:tint="?attr/color_on_main"
app:backgroundTint="?attr/color_main1_500"
app:shapeAppearanceOverlay="@style/rounded"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/send_area" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/unread_messages"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="-13dp"
android:layout_marginStart="-11dp"
android:gravity="center"
android:background="@drawable/shape_red_round"
android:text="@{String.valueOf(viewModel.unreadMessagesCount), default=`1`}"
android:textColor="?attr/color_on_main"
android:textSize="13sp"
android:visibility="@{viewModel.isUserScrollingUp &amp;&amp; viewModel.unreadMessagesCount > 0 ? View.VISIBLE : View.GONE}"
app:layout_constraintTop_toTopOf="@id/scroll_to_bottom"
app:layout_constraintStart_toEndOf="@id/scroll_to_bottom"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<include <include
android:id="@+id/message_bottom_sheet" android:id="@+id/long_press_menu"
layout="@layout/chat_message_bottom_sheet" /> android:visibility="@{messageLongPressViewModel.visible ? View.VISIBLE : View.GONE, default=gone}"
bind:viewModel="@{messageLongPressViewModel}"
layout="@layout/chat_bubble_long_press_menu" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </FrameLayout>
</layout> </layout>

View file

@ -25,6 +25,7 @@
<color name="gray_200">#EDEDED</color> <color name="gray_200">#EDEDED</color>
<color name="gray_200_night">#1F1F1F</color> <color name="gray_200_night">#1F1F1F</color>
<color name="gray_300">#C9C9C9</color> <color name="gray_300">#C9C9C9</color>
<color name="gray_300_alpha_40">#66C9C9C9</color>
<color name="gray_400">#949494</color> <color name="gray_400">#949494</color>
<color name="gray_500">#4E4E4E</color> <color name="gray_500">#4E4E4E</color>
<color name="gray_600">#2E3030</color> <color name="gray_600">#2E3030</color>

View file

@ -117,6 +117,26 @@
<item name="android:drawableTint">?attr/color_danger_500</item> <item name="android:drawableTint">?attr/color_danger_500</item>
<item name="android:drawablePadding">8dp</item> <item name="android:drawablePadding">8dp</item>
</style> </style>
<style name="popup_menu_action_label_style">
<item name="android:fontFamily">@font/noto_sans</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">?attr/color_main2_500</item>
<item name="android:maxLines">1</item>
<item name="android:ellipsize">end</item>
<item name="android:padding">15dp</item>
<item name="android:drawableTint">?attr/color_main2_500</item>
<item name="android:drawablePadding">8dp</item>
</style>
<style name="popup_menu_danger_action_label_style">
<item name="android:fontFamily">@font/noto_sans</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">?attr/color_danger_500</item>
<item name="android:maxLines">1</item>
<item name="android:ellipsize">end</item>
<item name="android:padding">15dp</item>
<item name="android:drawableTint">?attr/color_danger_500</item>
<item name="android:drawablePadding">8dp</item>
</style>
<style name="call_start_numpad_digits_style"> <style name="call_start_numpad_digits_style">
<item name="android:fontFamily">@font/noto_sans</item> <item name="android:fontFamily">@font/noto_sans</item>
<item name="android:textColor">@color/gray_main2_600</item> <item name="android:textColor">@color/gray_main2_600</item>