mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Using new SDK APIs to improve chat message search in conversation
This commit is contained in:
parent
8a410fc77f
commit
55cd29e710
7 changed files with 243 additions and 72 deletions
|
|
@ -34,6 +34,7 @@ import android.view.MotionEvent
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.PopupWindow
|
||||
import androidx.activity.result.PickVisualMediaRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
|
|
@ -108,7 +109,7 @@ open class ConversationFragment : SlidingPaneChildFragment() {
|
|||
|
||||
protected lateinit var sendMessageViewModel: SendMessageInConversationViewModel
|
||||
|
||||
protected lateinit var messageLongPressViewModel: ChatMessageLongPressViewModel
|
||||
private lateinit var messageLongPressViewModel: ChatMessageLongPressViewModel
|
||||
|
||||
private lateinit var adapter: ConversationEventAdapter
|
||||
|
||||
|
|
@ -211,6 +212,12 @@ open class ConversationFragment : SlidingPaneChildFragment() {
|
|||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
if (positionStart > 0) {
|
||||
adapter.notifyItemChanged(positionStart - 1) // For grouping purposes
|
||||
} else if (adapter.itemCount != itemCount) {
|
||||
if (viewModel.searchInProgress.value == true) {
|
||||
val recyclerView = binding.eventsList
|
||||
recyclerView.scrollToPosition(viewModel.itemToScrollTo.value ?: 0)
|
||||
viewModel.searchInProgress.postValue(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (viewModel.isUserScrollingUp.value == true) {
|
||||
|
|
@ -567,6 +574,15 @@ open class ConversationFragment : SlidingPaneChildFragment() {
|
|||
showUnsafeConversationDetailsBottomSheet()
|
||||
}
|
||||
|
||||
binding.searchField.setOnEditorActionListener { view, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
||||
view.hideKeyboard()
|
||||
viewModel.searchUp()
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
sendMessageViewModel.emojiToAddEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { emoji ->
|
||||
binding.sendArea.messageToSend.addCharacterAtPosition(emoji)
|
||||
|
|
@ -594,10 +610,6 @@ open class ConversationFragment : SlidingPaneChildFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
viewModel.searchFilter.observe(viewLifecycleOwner) { filter ->
|
||||
viewModel.applyFilter(filter.trim())
|
||||
}
|
||||
|
||||
viewModel.focusSearchBarEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { show ->
|
||||
if (show) {
|
||||
|
|
@ -665,6 +677,13 @@ open class ConversationFragment : SlidingPaneChildFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
viewModel.itemToScrollTo.observe(viewLifecycleOwner) { position ->
|
||||
if (position >= 0) {
|
||||
val recyclerView = binding.eventsList
|
||||
recyclerView.scrollToPosition(position)
|
||||
}
|
||||
}
|
||||
|
||||
messageLongPressViewModel.replyToMessageEvent.observe(viewLifecycleOwner) {
|
||||
it.consume {
|
||||
val model = messageLongPressViewModel.messageModel.value
|
||||
|
|
@ -757,7 +776,7 @@ open class ConversationFragment : SlidingPaneChildFragment() {
|
|||
sharedViewModel.forceRefreshConversationEvents.observe(viewLifecycleOwner) {
|
||||
it.consume {
|
||||
Log.i("$TAG Force refreshing messages list")
|
||||
viewModel.applyFilter("")
|
||||
viewModel.applyFilter()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -788,7 +807,9 @@ open class ConversationFragment : SlidingPaneChildFragment() {
|
|||
scrollListener = object : ConversationScrollListener(layoutManager) {
|
||||
@UiThread
|
||||
override fun onLoadMore(totalItemsCount: Int) {
|
||||
viewModel.loadMoreData(totalItemsCount)
|
||||
if (viewModel.searchInProgress.value == false) {
|
||||
viewModel.loadMoreData(totalItemsCount)
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ class EventLogModel @WorkerThread constructor(
|
|||
isFromGroup: Boolean = false,
|
||||
isGroupedWithPreviousOne: Boolean = false,
|
||||
isGroupedWithNextOne: Boolean = false,
|
||||
currentFilter: String = "",
|
||||
onContentClicked: ((fileModel: FileModel) -> Unit)? = null,
|
||||
onJoinConferenceClicked: ((uri: String) -> Unit)? = null,
|
||||
onWebUrlClicked: ((url: String) -> Unit)? = null,
|
||||
|
|
@ -82,6 +83,7 @@ class EventLogModel @WorkerThread constructor(
|
|||
chatMessage.isForward,
|
||||
isGroupedWithPreviousOne,
|
||||
isGroupedWithNextOne,
|
||||
currentFilter,
|
||||
onContentClicked,
|
||||
onJoinConferenceClicked,
|
||||
onWebUrlClicked,
|
||||
|
|
|
|||
|
|
@ -19,10 +19,12 @@
|
|||
*/
|
||||
package org.linphone.ui.main.chat.model
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.os.CountDownTimer
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import android.text.style.StyleSpan
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
|
|
@ -74,6 +76,7 @@ class MessageModel @WorkerThread constructor(
|
|||
val isForward: Boolean,
|
||||
isGroupedWithPreviousOne: Boolean,
|
||||
isGroupedWithNextOne: Boolean,
|
||||
private val currentFilter: String = "",
|
||||
private val onContentClicked: ((fileModel: FileModel) -> Unit)? = null,
|
||||
private val onJoinConferenceClicked: ((uri: String) -> Unit)? = null,
|
||||
private val onWebUrlClicked: ((url: String) -> Unit)? = null,
|
||||
|
|
@ -164,6 +167,8 @@ class MessageModel @WorkerThread constructor(
|
|||
MutableLiveData<Event<Boolean>>()
|
||||
}
|
||||
|
||||
var isTextHighlighted = false
|
||||
|
||||
private var voiceRecordAudioFocusRequest: AudioFocusRequestCompat? = null
|
||||
|
||||
private lateinit var voiceRecordPath: String
|
||||
|
|
@ -354,7 +359,7 @@ class MessageModel @WorkerThread constructor(
|
|||
displayableContentFound = true
|
||||
} else if (content.isText && !content.isFile) {
|
||||
Log.d("$TAG Found plain text content")
|
||||
computeTextContent(content)
|
||||
computeTextContent(content, currentFilter)
|
||||
|
||||
displayableContentFound = true
|
||||
} else if (content.isVoiceRecording) {
|
||||
|
|
@ -554,10 +559,39 @@ class MessageModel @WorkerThread constructor(
|
|||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun computeTextContent(content: Content) {
|
||||
fun highlightText(highlight: String) {
|
||||
if (isTextHighlighted && highlight.isEmpty()) {
|
||||
isTextHighlighted = false
|
||||
}
|
||||
|
||||
val textContent = chatMessage.contents.find {
|
||||
it.isText
|
||||
}
|
||||
if (textContent != null) {
|
||||
computeTextContent(textContent, highlight)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun computeTextContent(content: Content, highlight: String) {
|
||||
val textContent = content.utf8Text.orEmpty().trim()
|
||||
val spannableBuilder = SpannableStringBuilder(textContent)
|
||||
|
||||
// Check for search
|
||||
if (highlight.isNotEmpty()) {
|
||||
val indexStart = textContent.indexOf(highlight, 0, ignoreCase = true)
|
||||
if (indexStart >= 0) {
|
||||
isTextHighlighted = true
|
||||
val indexEnd = indexStart + highlight.length
|
||||
spannableBuilder.setSpan(
|
||||
StyleSpan(Typeface.BOLD),
|
||||
indexStart,
|
||||
indexEnd,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for mentions
|
||||
val chatRoom = chatMessage.chatRoom
|
||||
val matcher = Pattern.compile(MENTION_REGEXP).matcher(textContent)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import org.linphone.core.Address
|
|||
import org.linphone.core.ChatMessage
|
||||
import org.linphone.core.ChatMessageReaction
|
||||
import org.linphone.core.ChatRoom
|
||||
import org.linphone.core.ChatRoom.HistoryFilter
|
||||
import org.linphone.core.ChatRoomListenerStub
|
||||
import org.linphone.core.ConferenceScheduler
|
||||
import org.linphone.core.ConferenceSchedulerListenerStub
|
||||
|
|
@ -41,6 +42,7 @@ import org.linphone.core.Factory
|
|||
import org.linphone.core.Friend
|
||||
import org.linphone.core.Participant
|
||||
import org.linphone.core.ParticipantInfo
|
||||
import org.linphone.core.SearchDirection
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.ui.main.chat.model.EventLogModel
|
||||
import org.linphone.ui.main.chat.model.FileModel
|
||||
|
|
@ -59,6 +61,8 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo
|
|||
|
||||
const val MAX_TIME_TO_GROUP_MESSAGES = 60 // 1 minute
|
||||
const val SCROLLING_POSITION_NOT_SET = -1
|
||||
|
||||
const val ITEMS_TO_LOAD_BEFORE_SEARCH_RESULT = 6
|
||||
}
|
||||
|
||||
val showBackButton = MutableLiveData<Boolean>()
|
||||
|
|
@ -89,9 +93,13 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo
|
|||
|
||||
val searchFilter = MutableLiveData<String>()
|
||||
|
||||
val isUserScrollingUp = MutableLiveData<Boolean>()
|
||||
val searchInProgress = MutableLiveData<Boolean>()
|
||||
|
||||
val noMatchingResultForFilter = MutableLiveData<Boolean>()
|
||||
val canSearchDown = MutableLiveData<Boolean>()
|
||||
|
||||
val itemToScrollTo = MutableLiveData<Int>()
|
||||
|
||||
val isUserScrollingUp = MutableLiveData<Boolean>()
|
||||
|
||||
val unreadMessagesCount = MutableLiveData<Int>()
|
||||
|
||||
|
|
@ -127,6 +135,8 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo
|
|||
|
||||
var eventsList = arrayListOf<EventLogModel>()
|
||||
|
||||
private var latestMatch: EventLog? = null
|
||||
|
||||
private val chatRoomListener = object : ChatRoomListenerStub() {
|
||||
@WorkerThread
|
||||
override fun onConferenceJoined(chatRoom: ChatRoom, eventLog: EventLog) {
|
||||
|
|
@ -308,6 +318,9 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo
|
|||
searchBarVisible.value = false
|
||||
isUserScrollingUp.value = false
|
||||
isDisabledBecauseNotSecured.value = false
|
||||
searchInProgress.value = false
|
||||
canSearchDown.value = false
|
||||
itemToScrollTo.value = -1
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
|
|
@ -341,14 +354,33 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo
|
|||
|
||||
@UiThread
|
||||
fun closeSearchBar() {
|
||||
clearFilter()
|
||||
searchFilter.value = ""
|
||||
searchBarVisible.value = false
|
||||
focusSearchBarEvent.value = Event(false)
|
||||
latestMatch = null
|
||||
canSearchDown.value = false
|
||||
|
||||
coreContext.postOnCoreThread {
|
||||
for (eventLog in eventsList) {
|
||||
if ((eventLog.model as? MessageModel)?.isTextHighlighted == true) {
|
||||
eventLog.model.highlightText("")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun clearFilter() {
|
||||
searchFilter.value = ""
|
||||
fun searchUp() {
|
||||
coreContext.postOnCoreThread {
|
||||
searchChatMessage(SearchDirection.Up)
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun searchDown() {
|
||||
coreContext.postOnCoreThread {
|
||||
searchChatMessage(SearchDirection.Down)
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
|
|
@ -360,9 +392,9 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo
|
|||
}
|
||||
|
||||
@UiThread
|
||||
fun applyFilter(filter: String) {
|
||||
fun applyFilter() {
|
||||
coreContext.postOnCoreThread {
|
||||
computeEvents(filter)
|
||||
computeEvents()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -471,7 +503,7 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo
|
|||
}
|
||||
|
||||
val history = chatRoom.getHistoryRangeEvents(totalItemsCount, upperBound)
|
||||
val list = getEventsListFromHistory(history, searchFilter.value.orEmpty())
|
||||
val list = getEventsListFromHistory(history)
|
||||
|
||||
val lastEvent = list.lastOrNull()
|
||||
val newEvent = eventsList.firstOrNull()
|
||||
|
|
@ -574,21 +606,15 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo
|
|||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun computeEvents(filter: String = "") {
|
||||
private fun computeEvents() {
|
||||
eventsList.forEach(EventLogModel::destroy)
|
||||
|
||||
val history = chatRoom.getHistoryEvents(MESSAGES_PER_PAGE)
|
||||
val list = getEventsListFromHistory(history, filter)
|
||||
val list = getEventsListFromHistory(history)
|
||||
Log.i("$TAG Extracted [${list.size}] events from conversation history in database")
|
||||
eventsList = list
|
||||
updateEvents.postValue(Event(true))
|
||||
isEmpty.postValue(eventsList.isEmpty())
|
||||
|
||||
if (filter.isNotEmpty() && eventsList.isEmpty()) {
|
||||
noMatchingResultForFilter.postValue(true)
|
||||
} else {
|
||||
noMatchingResultForFilter.postValue(false)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
|
|
@ -619,8 +645,7 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo
|
|||
}
|
||||
|
||||
val newList = getEventsListFromHistory(
|
||||
eventsToAdd.toTypedArray(),
|
||||
searchFilter.value.orEmpty().trim()
|
||||
eventsToAdd.toTypedArray()
|
||||
)
|
||||
val newEvent = newList.firstOrNull()
|
||||
|
||||
|
|
@ -639,6 +664,38 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo
|
|||
isEmpty.postValue(eventsList.isEmpty())
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun prependEvents(eventLogs: Array<EventLog>) {
|
||||
Log.i("$TAG Prepending [${eventLogs.size}] events")
|
||||
// Need to use a new list, otherwise ConversationFragment's dataObserver isn't triggered...
|
||||
val list = arrayListOf<EventLogModel>()
|
||||
val firstEvent = eventsList.firstOrNull()
|
||||
|
||||
// Prevents message duplicates
|
||||
val eventsToAdd = arrayListOf<EventLog>()
|
||||
eventsToAdd.addAll(eventLogs)
|
||||
|
||||
val newList = getEventsListFromHistory(
|
||||
eventsToAdd.toTypedArray()
|
||||
)
|
||||
val lastEvent = newList.lastOrNull()
|
||||
|
||||
if (lastEvent != null && lastEvent.model is MessageModel && firstEvent != null && firstEvent.model is MessageModel && shouldWeGroupTwoEvents(
|
||||
firstEvent.eventLog,
|
||||
lastEvent.eventLog
|
||||
)
|
||||
) {
|
||||
lastEvent.model.groupedWithNextMessage.postValue(true)
|
||||
firstEvent.model.groupedWithPreviousMessage.postValue(true)
|
||||
}
|
||||
|
||||
list.addAll(newList)
|
||||
list.addAll(eventsList)
|
||||
eventsList = list
|
||||
updateEvents.postValue(Event(true))
|
||||
isEmpty.postValue(eventsList.isEmpty())
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun processGroupedEvents(
|
||||
groupedEventLogs: ArrayList<EventLog>
|
||||
|
|
@ -657,6 +714,7 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo
|
|||
groupChatRoom,
|
||||
index > 0,
|
||||
index != groupedEventLogs.size - 1,
|
||||
searchFilter.value.orEmpty(),
|
||||
{ fileModel ->
|
||||
fileToDisplayEvent.postValue(Event(fileModel))
|
||||
},
|
||||
|
|
@ -683,8 +741,7 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo
|
|||
|
||||
@WorkerThread
|
||||
private fun getEventsListFromHistory(
|
||||
history: Array<EventLog>,
|
||||
filter: String = ""
|
||||
history: Array<EventLog>
|
||||
): ArrayList<EventLogModel> {
|
||||
val eventsList = arrayListOf<EventLogModel>()
|
||||
val groupedEventLogs = arrayListOf<EventLog>()
|
||||
|
|
@ -695,25 +752,6 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo
|
|||
eventsList.addAll(processGroupedEvents(arrayListOf(event)))
|
||||
} else {
|
||||
for (event in history) {
|
||||
if (filter.isNotEmpty()) {
|
||||
if (event.type == EventLog.Type.ConferenceChatMessage) {
|
||||
val message = event.chatMessage ?: continue
|
||||
val fromAddress = message.fromAddress
|
||||
val model = coreContext.contactsManager.getContactAvatarModelForAddress(
|
||||
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
|
||||
|
|
@ -814,6 +852,75 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo
|
|||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun searchChatMessage(direction: SearchDirection) {
|
||||
searchInProgress.postValue(true)
|
||||
|
||||
val textToSearch = searchFilter.value.orEmpty().trim()
|
||||
val match = chatRoom.searchChatMessageByText(textToSearch, latestMatch, direction)
|
||||
if (match == null) {
|
||||
Log.i(
|
||||
"$TAG No match found while looking up for message with text [$textToSearch] in direction [$direction] starting from message [${latestMatch?.chatMessage?.messageId}]"
|
||||
)
|
||||
searchInProgress.postValue(false)
|
||||
val message = if (latestMatch == null) {
|
||||
R.string.conversation_search_no_match_found
|
||||
} else {
|
||||
R.string.conversation_search_no_more_match
|
||||
}
|
||||
showRedToastEvent.postValue(
|
||||
Event(
|
||||
Pair(message, R.drawable.magnifying_glass)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Log.i(
|
||||
"$TAG Found result [${match.chatMessage?.messageId}] while looking up for message with text [$textToSearch] in direction [$direction] starting from message [${latestMatch?.chatMessage?.messageId}]"
|
||||
)
|
||||
latestMatch = match
|
||||
|
||||
val found = eventsList.find {
|
||||
it.eventLog == match
|
||||
}
|
||||
if (found == null) {
|
||||
Log.i("$TAG Found result isn't in currently loaded history, loading missing events")
|
||||
val historyToAdd = chatRoom.getHistoryRangeBetween(
|
||||
latestMatch,
|
||||
eventsList[0].eventLog,
|
||||
HistoryFilter.None.toInt()
|
||||
)
|
||||
Log.i("$TAG Loaded [${historyToAdd.size}] items from history")
|
||||
|
||||
Log.i(
|
||||
"$TAG Also loading [$ITEMS_TO_LOAD_BEFORE_SEARCH_RESULT] items before the match"
|
||||
)
|
||||
val previousMessages = chatRoom.getHistoryRangeNear(
|
||||
ITEMS_TO_LOAD_BEFORE_SEARCH_RESULT,
|
||||
0,
|
||||
match,
|
||||
HistoryFilter.None.toInt()
|
||||
)
|
||||
|
||||
itemToScrollTo.postValue(previousMessages.size - 1)
|
||||
val toAdd = previousMessages.plus(historyToAdd)
|
||||
prependEvents(toAdd)
|
||||
} else {
|
||||
Log.i("$TAG Found result is already in history, no need to load more history")
|
||||
(found.model as? MessageModel)?.highlightText(textToSearch)
|
||||
val index = eventsList.indexOf(found)
|
||||
if (direction == SearchDirection.Down && index < eventsList.size - 1) {
|
||||
// Go to next message to prevent the message we are looking for to be behind the scroll to bottom button
|
||||
itemToScrollTo.postValue(index + 1)
|
||||
} else {
|
||||
itemToScrollTo.postValue(index)
|
||||
}
|
||||
searchInProgress.postValue(false)
|
||||
}
|
||||
|
||||
canSearchDown.postValue(true)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun createGroupCall() {
|
||||
val core = coreContext.core
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
<androidx.constraintlayout.widget.Group
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="cancel_search, search, clear_field"
|
||||
app:constraint_referenced_ids="cancel_search, search, search_up, search_down"
|
||||
android:visibility="@{viewModel.searchBarVisible ? View.VISIBLE : View.GONE, default=gone}" />
|
||||
|
||||
<ImageView
|
||||
|
|
@ -198,36 +198,49 @@
|
|||
app:hintTextColor="?attr/color_main2_400"
|
||||
app:boxStrokeWidth="0dp"
|
||||
app:boxStrokeWidthFocused="0dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/clear_field"
|
||||
app:layout_constraintEnd_toStartOf="@id/search_up"
|
||||
app:layout_constraintStart_toEndOf="@id/cancel_search"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/search_field"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:textCursorDrawable="@null"
|
||||
android:textSize="16sp"
|
||||
android:inputType="text"
|
||||
android:paddingVertical="1dp"
|
||||
android:imeOptions="actionSearch"
|
||||
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:id="@+id/search_up"
|
||||
android:onClick="@{() -> viewModel.searchUp()}"
|
||||
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"
|
||||
android:layout_height="0dp"
|
||||
android:padding="15dp"
|
||||
android:src="@drawable/caret_up"
|
||||
app:layout_constraintBottom_toBottomOf="@id/search"
|
||||
app:layout_constraintEnd_toStartOf="@id/search_down"
|
||||
app:layout_constraintTop_toTopOf="@id/search"
|
||||
app:tint="@color/icon_color_selector" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/search_down"
|
||||
android:onClick="@{() -> viewModel.searchDown()}"
|
||||
android:enabled="@{viewModel.searchFilter.length() > 0 && viewModel.canSearchDown}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:padding="15dp"
|
||||
android:src="@drawable/caret_down"
|
||||
app:layout_constraintBottom_toBottomOf="@id/search"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/search"
|
||||
app:tint="?attr/color_main2_500" />
|
||||
app:tint="@color/icon_color_selector" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/events_list"
|
||||
|
|
@ -249,20 +262,6 @@
|
|||
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"
|
||||
|
|
@ -340,6 +339,10 @@
|
|||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<include
|
||||
layout="@layout/operation_in_progress"
|
||||
bind:visibility="@{viewModel.searchInProgress}" />
|
||||
|
||||
<include
|
||||
android:id="@+id/long_press_menu"
|
||||
android:visibility="@{messageLongPressViewModel.visible ? View.VISIBLE : View.GONE, default=gone}"
|
||||
|
|
|
|||
|
|
@ -462,6 +462,8 @@
|
|||
<string name="conversation_group_left_toast">Vous avez quitté la conversation</string>
|
||||
<string name="conversation_no_app_registered_to_handle_content_type_error_toast">Aucune application trouvée pour lire ce fichier</string>
|
||||
<string name="conversation_to_display_no_found_toast">Conversation non trouvée</string>
|
||||
<string name="conversation_search_no_match_found">Aucun résultat trouvé</string>
|
||||
<string name="conversation_search_no_more_match">Dernier résultat atteint</string>
|
||||
|
||||
<string name="conversation_info_participants_list_title">Membres du groupe</string>
|
||||
<string name="conversation_info_add_participants_label">Ajouter des membres</string>
|
||||
|
|
|
|||
|
|
@ -500,6 +500,8 @@
|
|||
<string name="conversation_group_left_toast">You have left the group</string>
|
||||
<string name="conversation_no_app_registered_to_handle_content_type_error_toast">No app found to open this kind of file</string>
|
||||
<string name="conversation_to_display_no_found_toast">Conversation was not found</string>
|
||||
<string name="conversation_search_no_match_found">No matching result found</string>
|
||||
<string name="conversation_search_no_more_match">Last matching result reached</string>
|
||||
|
||||
<string name="conversation_info_participants_list_title">Group members</string>
|
||||
<string name="conversation_info_add_participants_label">Add participants</string>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue