Added search in conversation top bar

This commit is contained in:
Sylvain Berfini 2023-10-16 16:11:18 +02:00
parent a2355e3225
commit f9d2e04609
15 changed files with 250 additions and 81 deletions

View file

@ -152,7 +152,9 @@ class ConversationEventAdapter(
return if (!oldItem.isEvent && !newItem.isEvent) {
val oldData = (oldItem.model as ChatMessageModel)
val newData = (newItem.model as ChatMessageModel)
oldData.time == newData.time && oldData.isOutgoing == newData.isOutgoing
oldData.id == newData.id &&
oldData.timestamp == newData.timestamp &&
oldData.isOutgoing == newData.isOutgoing
} else {
oldItem.notifyId == newItem.notifyId
}
@ -162,8 +164,8 @@ class ConversationEventAdapter(
return if (oldItem.isEvent && newItem.isEvent) {
true
} else if (!oldItem.isEvent && !newItem.isEvent) {
val oldModel = (newItem.model as ChatMessageModel)
val newModel = newItem.model
val oldModel = (oldItem.model as ChatMessageModel)
val newModel = (newItem.model as ChatMessageModel)
oldModel.statusIcon.value == newModel.statusIcon.value
} else {
false

View file

@ -51,6 +51,8 @@ import org.linphone.ui.main.chat.viewmodel.ConversationViewModel
import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.utils.AppUtils
import org.linphone.utils.Event
import org.linphone.utils.hideKeyboard
import org.linphone.utils.showKeyboard
@UiThread
class ConversationFragment : GenericFragment() {
@ -176,6 +178,21 @@ class ConversationFragment : GenericFragment() {
)
findNavController().navigate(action)
}
viewModel.searchFilter.observe(viewLifecycleOwner) { filter ->
viewModel.applyFilter(filter.trim())
}
viewModel.focusSearchBarEvent.observe(viewLifecycleOwner) {
it.consume { show ->
if (show) {
// To automatically open keyboard
binding.search.showKeyboard()
} else {
binding.search.hideKeyboard()
}
}
}
}
private fun showChatMessageLongPressMenu(chatMessageModel: ChatMessageModel) {

View file

@ -53,7 +53,9 @@ class ChatMessageModel @WorkerThread constructor(
val text = LinphoneUtils.getTextDescribingMessage(chatMessage)
val time = TimestampUtils.toString(chatMessage.time)
val timestamp = chatMessage.time
val time = TimestampUtils.toString(timestamp)
val chatRoomIsReadOnly = chatMessage.chatRoom.isReadOnly

View file

@ -22,6 +22,5 @@ package org.linphone.ui.main.chat.model
import org.linphone.core.Friend
import org.linphone.ui.main.contacts.model.ContactAvatarModel
class ParticipantModel(friend: Friend, val isMyselfAdmin: Boolean, val isParticipantAdmin: Boolean) : ContactAvatarModel(
friend
)
class ParticipantModel(friend: Friend, val isMyselfAdmin: Boolean, val isParticipantAdmin: Boolean) :
ContactAvatarModel(friend)

View file

@ -70,17 +70,17 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() {
private val chatRoomListener = object : ChatRoomListenerStub() {
@WorkerThread
override fun onParticipantAdded(chatRoom: ChatRoom, eventLog: EventLog) {
computeParticipantsList(isGroup.value == true)
computeParticipantsList()
}
@WorkerThread
override fun onParticipantRemoved(chatRoom: ChatRoom, eventLog: EventLog) {
computeParticipantsList(isGroup.value == true)
computeParticipantsList()
}
@WorkerThread
override fun onParticipantAdminStatusChanged(chatRoom: ChatRoom, eventLog: EventLog) {
computeParticipantsList(isGroup.value == true)
computeParticipantsList()
}
@WorkerThread
@ -183,8 +183,7 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() {
isMyselfAdmin.postValue(chatRoom.me?.isAdmin)
val isGroupChatRoom = !chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt()) &&
chatRoom.hasCapability(ChatRoom.Capabilities.Conference.toInt())
val isGroupChatRoom = isChatRoomAGroup()
isGroup.postValue(isGroupChatRoom)
val empty = chatRoom.hasCapability(ChatRoom.Capabilities.Conference.toInt()) && chatRoom.participants.isEmpty()
@ -196,14 +195,16 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() {
subject.postValue(chatRoom.subject)
computeParticipantsList(isGroupChatRoom)
computeParticipantsList()
}
@WorkerThread
private fun computeParticipantsList(isGroupChatRoom: Boolean) {
private fun computeParticipantsList() {
avatarModel.value?.destroy()
avatarsMap.values.forEach(ParticipantModel::destroy)
val groupChatRoom = isChatRoomAGroup()
val friends = arrayListOf<Friend>()
val participantsList = arrayListOf<ParticipantModel>()
if (chatRoom.hasCapability(ChatRoom.Capabilities.Basic.toInt())) {
@ -214,14 +215,14 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() {
for (participant in chatRoom.participants) {
val model = getParticipantModelForAddress(
participant.address,
if (isGroup.value == true) participant.isAdmin else false
if (groupChatRoom) participant.isAdmin else false
)
friends.add(model.friend)
participantsList.add(model)
}
}
val avatar = if (isGroupChatRoom) {
val avatar = if (groupChatRoom) {
val fakeFriend = coreContext.core.createFriend()
ContactAvatarModel(fakeFriend)
} else {
@ -260,4 +261,13 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() {
avatarsMap[key] = avatar
return avatar
}
@WorkerThread
private fun isChatRoomAGroup(): Boolean {
return if (::chatRoom.isInitialized) {
LinphoneUtils.isChatRoomAGroup(chatRoom)
} else {
false
}
}
}

View file

@ -62,10 +62,20 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
val textToSend = MutableLiveData<String>()
val searchBarVisible = MutableLiveData<Boolean>()
val searchFilter = MutableLiveData<String>()
val focusSearchBarEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
val chatRoomFoundEvent = MutableLiveData<Event<Boolean>>()
private lateinit var chatRoom: ChatRoom
private var currentFilter = ""
private val avatarsMap = hashMapOf<String, ContactAvatarModel>()
private val chatRoomListener = object : ChatRoomListenerStub() {
@ -84,7 +94,7 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
} else {
false
}
list.add(EventLogModel(eventLog, avatarModel, isGroup.value == true, group, true))
list.add(EventLogModel(eventLog, avatarModel, isChatRoomAGroup(), group, true))
events.postValue(list)
}
@ -118,7 +128,7 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
val newList = getEventsListFromHistory(
eventLogs,
isGroupChatRoom = isGroup.value == true
searchFilter.value.orEmpty().trim()
)
list.addAll(newList)
@ -128,6 +138,10 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
}
}
init {
searchBarVisible.value = false
}
override fun onCleared() {
super.onCleared()
@ -139,6 +153,24 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
}
}
@UiThread
fun openSearchBar() {
searchBarVisible.value = true
focusSearchBarEvent.value = Event(true)
}
@UiThread
fun closeSearchBar() {
clearFilter()
searchBarVisible.value = false
focusSearchBarEvent.value = Event(false)
}
@UiThread
fun clearFilter() {
searchFilter.value = ""
}
@UiThread
fun findChatRoom(localSipUri: String, remoteSipUri: String) {
coreContext.postOnCoreThread { core ->
@ -174,6 +206,13 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
}
}
@UiThread
fun applyFilter(filter: String) {
coreContext.postOnCoreThread {
computeEvents(filter)
}
}
@UiThread
fun sendMessage() {
coreContext.postOnCoreThread { core ->
@ -215,10 +254,6 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
private fun configureChatRoom() {
computeComposingLabel()
val isGroupChatRoom = !chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt()) &&
chatRoom.hasCapability(ChatRoom.Capabilities.Conference.toInt())
isGroup.postValue(isGroupChatRoom)
val empty = chatRoom.hasCapability(ChatRoom.Capabilities.Conference.toInt()) && chatRoom.participants.isEmpty()
val readOnly = chatRoom.isReadOnly || empty
isReadOnly.postValue(readOnly)
@ -226,6 +261,9 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
Log.w("$TAG Chat room with subject [${chatRoom.subject}] is read only!")
}
val group = isChatRoomAGroup()
isGroup.postValue(group)
subject.postValue(chatRoom.subject)
val friends = arrayListOf<Friend>()
@ -243,7 +281,7 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
firstParticipant?.address ?: chatRoom.peerAddress
}
val avatar = if (isGroupChatRoom) {
val avatar = if (group) {
val fakeFriend = coreContext.core.createFriend()
ContactAvatarModel(fakeFriend)
} else {
@ -252,17 +290,22 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
avatar.setPicturesFromFriends(friends)
avatarModel.postValue(avatar)
val history = chatRoom.getHistoryEvents(0)
val eventsList = getEventsListFromHistory(history, isGroupChatRoom)
events.postValue(eventsList)
computeEvents()
chatRoom.markAsRead()
}
@WorkerThread
private fun computeEvents(filter: String = "") {
events.value.orEmpty().forEach(EventLogModel::destroy)
val history = chatRoom.getHistoryEvents(0)
val eventsList = getEventsListFromHistory(history, filter)
events.postValue(eventsList)
}
@WorkerThread
private fun processGroupedEvents(
groupedEventLogs: ArrayList<EventLog>,
isGroupChatRoom: Boolean
groupedEventLogs: ArrayList<EventLog>
): ArrayList<EventLogModel> {
val eventsList = arrayListOf<EventLogModel>()
@ -273,7 +316,7 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
val model = EventLogModel(
groupedEvent,
avatar,
isGroupChatRoom,
isChatRoomAGroup(),
index > 0,
index == groupedEventLogs.size - 1
)
@ -286,10 +329,26 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
}
@WorkerThread
private fun getEventsListFromHistory(history: Array<EventLog>, isGroupChatRoom: Boolean): ArrayList<EventLogModel> {
private fun getEventsListFromHistory(history: Array<EventLog>, filter: String = ""): ArrayList<EventLogModel> {
val eventsList = arrayListOf<EventLogModel>()
val groupedEventLogs = arrayListOf<EventLog>()
for (event in history) {
// TODO: let the SDK do it
if (event.type == EventLog.Type.ConferenceChatMessage) {
val message = event.chatMessage ?: continue
val fromAddress = message.fromAddress
val model = getAvatarModelForAddress(fromAddress)
if (
!model.name.value.orEmpty().contains(filter, ignoreCase = true) &&
!fromAddress.asStringUriOnly().contains(filter, ignoreCase = true) &&
!message.utf8Text.orEmpty().contains(filter, ignoreCase = true)
) {
continue
}
} else {
continue
}
if (groupedEventLogs.isEmpty()) {
groupedEventLogs.add(event)
continue
@ -299,7 +358,7 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
val groupEvents = shouldWeGroupTwoEvents(event, previousGroupEvent)
if (!groupEvents) {
eventsList.addAll(processGroupedEvents(groupedEventLogs, isGroupChatRoom))
eventsList.addAll(processGroupedEvents(groupedEventLogs))
groupedEventLogs.clear()
}
@ -307,7 +366,7 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
}
if (groupedEventLogs.isNotEmpty()) {
eventsList.addAll(processGroupedEvents(groupedEventLogs, isGroupChatRoom))
eventsList.addAll(processGroupedEvents(groupedEventLogs))
groupedEventLogs.clear()
}
@ -380,4 +439,13 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
composingLabel.postValue("")
}
}
@WorkerThread
private fun isChatRoomAGroup(): Boolean {
return if (::chatRoom.isInitialized) {
LinphoneUtils.isChatRoomAGroup(chatRoom)
} else {
false
}
}
}

View file

@ -199,6 +199,12 @@ class LinphoneUtils {
return "${localSipUri.asStringUriOnly()}~${remoteSipUri.asStringUriOnly()}"
}
@WorkerThread
fun isChatRoomAGroup(chatRoom: ChatRoom): Boolean {
return !chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt()) &&
chatRoom.hasCapability(ChatRoom.Capabilities.Conference.toInt())
}
@WorkerThread
fun getRecordingFilePathForAddress(address: Address): String {
val displayName = getDisplayName(address)

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:topRightRadius="16dp" android:bottomRightRadius="16dp" android:bottomLeftRadius="16dp" />
<solid android:color="@color/gray_100"/>
<solid android:color="@color/white"/>
</shape>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="16dp" />
<solid android:color="@color/gray_100"/>
<solid android:color="@color/white"/>
</shape>

View file

@ -64,12 +64,12 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<org.linphone.ui.main.chat.view.ChatBubbleTextView
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/text_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginStart="26dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="16dp"
android:paddingBottom="@{model.groupedWithNextOne ? @dimen/chat_bubble_text_padding_with_status : @dimen/chat_bubble_text_padding_with_bubble, default=@dimen/chat_bubble_text_padding_with_status}"
@ -104,7 +104,6 @@
android:layout_width="@dimen/small_icon_size"
android:layout_height="@dimen/small_icon_size"
android:layout_marginStart="5dp"
android:layout_marginTop="2dp"
android:src="@{model.statusIcon, default=@drawable/checks}"
android:visibility="@{model.isGroupedWithNextOne ? View.VISIBLE : View.GONE}"
app:layout_constraintTop_toTopOf="@id/date_time"

View file

@ -36,6 +36,25 @@
android:layout_marginBottom="80dp"
android:background="@color/white">
<androidx.constraintlayout.widget.Group
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="avatar, title, search_toggle, info"
android:visibility="@{viewModel.searchBarVisible ? View.GONE : View.VISIBLE}" />
<androidx.constraintlayout.widget.Group
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="cancel_search, search, clear_field"
android:visibility="@{viewModel.searchBarVisible ? View.VISIBLE : View.GONE, default=gone}" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/top_bar_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="title, search" />
<ImageView
android:id="@+id/back"
android:layout_width="wrap_content"
@ -43,7 +62,7 @@
android:padding="15dp"
android:adjustViewBounds="true"
android:onClick="@{backClickListener}"
android:visibility="@{viewModel.showBackButton ? View.VISIBLE : View.GONE}"
android:visibility="@{viewModel.showBackButton &amp;&amp; !viewModel.searchBarVisible ? View.VISIBLE : View.GONE}"
android:src="@drawable/caret_left"
app:tint="@color/orange_main_500"
app:layout_constraintBottom_toBottomOf="@id/title"
@ -68,7 +87,7 @@
android:layout_width="@dimen/avatar_presence_badge_size"
android:layout_height="@dimen/avatar_presence_badge_size"
android:src="@{viewModel.avatarModel.trust == SecurityLevel.Safe ? @drawable/trusted : @drawable/not_trusted, default=@drawable/trusted}"
android:visibility="@{viewModel.avatarModel.trust == SecurityLevel.Safe || viewModel.avatarModel.trust == SecurityLevel.Unsafe ? View.VISIBLE : View.GONE}"
android:visibility="@{!viewModel.searchBarVisible &amp;&amp; (viewModel.avatarModel.trust == SecurityLevel.Safe || viewModel.avatarModel.trust == SecurityLevel.Unsafe) ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toStartOf="@id/avatar"
app:layout_constraintBottom_toBottomOf="@id/avatar"/>
@ -78,14 +97,14 @@
android:layout_width="0dp"
android:layout_height="@dimen/top_bar_height"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_marginEnd="5dp"
android:maxLines="1"
android:ellipsize="end"
android:text="@{viewModel.isGroup ? viewModel.subject : viewModel.avatarModel.name, default=`John Doe`}"
android:textSize="16sp"
android:textColor="@color/gray_main2_600"
android:gravity="center_vertical"
app:layout_constraintEnd_toStartOf="@id/call"
app:layout_constraintEnd_toStartOf="@id/search_toggle"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toTopOf="parent"/>
@ -99,38 +118,82 @@
android:src="@drawable/info"
app:layout_constraintTop_toTopOf="@id/title"
app:layout_constraintBottom_toBottomOf="@id/title"
app:layout_constraintEnd_toEndOf="parent"/>
app:layout_constraintEnd_toEndOf="parent"
app:tint="@color/gray_main2_500"/>
<ImageView
android:id="@+id/video_call"
android:onClick="@{startVideoCallClickListener}"
android:id="@+id/search_toggle"
android:onClick="@{() -> viewModel.openSearchBar()}"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:padding="15dp"
android:adjustViewBounds="true"
android:src="@drawable/video_camera"
app:layout_constraintTop_toTopOf="@id/title"
android:src="@drawable/magnifying_glass"
app:layout_constraintBottom_toBottomOf="@id/title"
app:layout_constraintEnd_toStartOf="@id/info" />
app:layout_constraintEnd_toStartOf="@id/info"
app:layout_constraintTop_toTopOf="@id/title"
app:tint="@color/gray_main2_500" />
<ImageView
android:id="@+id/call"
android:onClick="@{startCallClickListener}"
android:id="@+id/cancel_search"
android:onClick="@{() -> viewModel.closeSearchBar()}"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:padding="15dp"
android:adjustViewBounds="true"
android:src="@drawable/phone"
app:layout_constraintTop_toTopOf="@id/title"
app:layout_constraintBottom_toBottomOf="@id/title"
app:layout_constraintEnd_toStartOf="@id/video_call"/>
android:src="@drawable/caret_left"
app:layout_constraintBottom_toBottomOf="@id/search"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/search"
app:tint="@color/gray_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="@color/gray_main2_400"
app:hintEnabled="false"
app:hintAnimationEnabled="false"
app:hintTextColor="@color/gray_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"
app:layout_constraintBottom_toBottomOf="@id/search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/search"
app:tint="@color/gray_main2_500" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/events_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="5dp"
app:layout_constraintTop_toBottomOf="@id/title"
android:paddingBottom="5dp"
android:background="@color/gray_100"
app:layout_constraintTop_toBottomOf="@id/top_bar_barrier"
app:layout_constraintBottom_toTopOf="@id/composing" />
<androidx.appcompat.widget.AppCompatTextView
@ -139,7 +202,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginBottom="5dp"
android:paddingBottom="5dp"
android:background="@color/gray_100"
android:text="@{viewModel.composingLabel, default=`John Doe is composing...`}"
android:textSize="12sp"
android:textColor="@color/gray_main2_400"

View file

@ -55,11 +55,11 @@
android:id="@+id/thumbs_up"
android:onClick="@{() -> model.sendReaction(@string/emoji_thumbs_up)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="10dp"
android:text="@string/emoji_thumbs_up"
android:textSize="30sp"
android:textSize="@dimen/chat_bubble_long_press_emoji_reaction_size"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintTop_toTopOf="@id/emojis_background"
app:layout_constraintBottom_toBottomOf="@id/emojis_background"
@ -71,12 +71,13 @@
android:id="@+id/love"
android:onClick="@{() -> model.sendReaction(@string/emoji_love)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="0dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:text="@string/emoji_love"
android:textSize="30sp"
android:textSize="@dimen/chat_bubble_long_press_emoji_reaction_size"
app:layout_constraintTop_toTopOf="@id/thumbs_up"
app:layout_constraintBottom_toBottomOf="@id/emojis_background"
app:layout_constraintStart_toEndOf="@id/thumbs_up"
app:layout_constraintEnd_toStartOf="@id/laughing"/>
@ -85,12 +86,13 @@
android:id="@+id/laughing"
android:onClick="@{() -> model.sendReaction(@string/emoji_laughing)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="0dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:text="@string/emoji_laughing"
android:textSize="30sp"
android:textSize="@dimen/chat_bubble_long_press_emoji_reaction_size"
app:layout_constraintTop_toTopOf="@id/thumbs_up"
app:layout_constraintBottom_toBottomOf="@id/emojis_background"
app:layout_constraintStart_toEndOf="@id/love"
app:layout_constraintEnd_toStartOf="@id/surprised"/>
@ -99,12 +101,13 @@
android:id="@+id/surprised"
android:onClick="@{() -> model.sendReaction(@string/emoji_surprised)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="0dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:text="@string/emoji_surprised"
android:textSize="30sp"
android:textSize="@dimen/chat_bubble_long_press_emoji_reaction_size"
app:layout_constraintTop_toTopOf="@id/thumbs_up"
app:layout_constraintBottom_toBottomOf="@id/emojis_background"
app:layout_constraintStart_toEndOf="@id/laughing"
app:layout_constraintEnd_toStartOf="@id/tear"/>
@ -113,12 +116,13 @@
android:id="@+id/tear"
android:onClick="@{() -> model.sendReaction(@string/emoji_tear)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="0dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:text="@string/emoji_tear"
android:textSize="30sp"
android:textSize="@dimen/chat_bubble_long_press_emoji_reaction_size"
app:layout_constraintTop_toTopOf="@id/thumbs_up"
app:layout_constraintBottom_toBottomOf="@id/emojis_background"
app:layout_constraintStart_toEndOf="@id/surprised"
app:layout_constraintEnd_toStartOf="@id/plus"/>
@ -133,7 +137,7 @@
app:layout_constraintStart_toEndOf="@id/tear"
app:layout_constraintEnd_toEndOf="@id/emojis_background"
app:layout_constraintTop_toTopOf="@id/thumbs_up"
app:layout_constraintBottom_toBottomOf="@id/thumbs_up" />
app:layout_constraintBottom_toBottomOf="@id/emojis_background" />
<include
android:id="@+id/bubble"

View file

@ -74,9 +74,8 @@
android:id="@+id/search_toggle"
android:onClick="@{() -> viewModel.openSearchBar()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="6dp"
android:layout_marginEnd="9dp"
android:layout_height="0dp"
android:padding="15dp"
android:src="@drawable/magnifying_glass"
app:layout_constraintBottom_toBottomOf="@id/title"
app:layout_constraintEnd_toEndOf="parent"
@ -87,9 +86,8 @@
android:id="@+id/cancel_search"
android:onClick="@{() -> viewModel.closeSearchBar()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_marginStart="5dp"
android:layout_height="0dp"
android:padding="15dp"
android:src="@drawable/caret_left"
app:layout_constraintBottom_toBottomOf="@id/search"
app:layout_constraintStart_toStartOf="parent"
@ -130,9 +128,8 @@
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:layout_height="0dp"
android:padding="15dp"
android:src="@drawable/x"
app:layout_constraintBottom_toBottomOf="@id/search"
app:layout_constraintEnd_toEndOf="parent"

View file

@ -5,7 +5,7 @@
<dimen name="call_all_actions_menu_height">235dp</dimen> <!-- sum of above two -->
<dimen name="landscape_nav_bar_width">75dp</dimen>
<dimen name="sliding_pane_left_fragment_width">325dp</dimen>
<dimen name="sliding_pane_left_fragment_width">425dp</dimen>
<!-- This value is the result of the above two added together -->
<dimen name="sliding_pane_left_fragment_with_nav_width">400dp</dimen>
<dimen name="sliding_pane_left_fragment_with_nav_width">500dp</dimen>
</resources>

View file

@ -65,4 +65,5 @@
<dimen name="chat_bubble_long_press_menu_bubble_offset">110dp</dimen>
<dimen name="chat_bubble_text_padding_with_bubble">12dp</dimen>
<dimen name="chat_bubble_text_padding_with_status">5dp</dimen>
<dimen name="chat_bubble_long_press_emoji_reaction_size">30sp</dimen>
</resources>