Enhance filtering to support threads

This commit is contained in:
ariskotsomitopoulos 2022-01-17 19:22:22 +02:00
parent 81a1dfd66d
commit 5e23947419
5 changed files with 138 additions and 2 deletions

View file

@ -19,6 +19,10 @@ package org.matrix.android.sdk.internal.session.search
import org.matrix.android.sdk.api.session.search.EventAndSender
import org.matrix.android.sdk.api.session.search.SearchResult
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.internal.database.RealmSessionProvider
import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.search.request.SearchRequestBody
@ -28,6 +32,7 @@ import org.matrix.android.sdk.internal.session.search.request.SearchRequestFilte
import org.matrix.android.sdk.internal.session.search.request.SearchRequestOrder
import org.matrix.android.sdk.internal.session.search.request.SearchRequestRoomEvents
import org.matrix.android.sdk.internal.session.search.response.SearchResponse
import org.matrix.android.sdk.internal.session.search.response.SearchResponseItem
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
@ -47,7 +52,8 @@ internal interface SearchTask : Task<SearchTask.Params, SearchResult> {
internal class DefaultSearchTask @Inject constructor(
private val searchAPI: SearchAPI,
private val globalErrorReceiver: GlobalErrorReceiver
private val globalErrorReceiver: GlobalErrorReceiver,
private val realmSessionProvider: RealmSessionProvider
) : SearchTask {
override suspend fun execute(params: SearchTask.Params): SearchResult {
@ -74,12 +80,22 @@ internal class DefaultSearchTask @Inject constructor(
}
private fun SearchResponse.toDomain(): SearchResult {
val localTimelineEvents = findRootThreadEventsFromDB(searchCategories.roomEvents?.results)
return SearchResult(
nextBatch = searchCategories.roomEvents?.nextBatch,
highlights = searchCategories.roomEvents?.highlights,
results = searchCategories.roomEvents?.results?.map { searchResponseItem ->
val localThreadEventDetails = localTimelineEvents
?.firstOrNull { it.eventId == searchResponseItem.event.eventId }
?.root
?.asDomain()
?.threadDetails
EventAndSender(
searchResponseItem.event,
searchResponseItem.event.apply {
threadDetails = localThreadEventDetails
},
searchResponseItem.event.senderId?.let { senderId ->
searchResponseItem.context?.profileInfo?.get(senderId)
?.let {
@ -94,4 +110,19 @@ internal class DefaultSearchTask @Inject constructor(
}?.reversed()
)
}
/**
* Find local events if exists in order to enhance the result with thread summary
*/
private fun findRootThreadEventsFromDB(searchResponseItemList: List<SearchResponseItem>?): List<TimelineEventEntity>? {
return realmSessionProvider.withRealm { realm ->
searchResponseItemList?.mapNotNull {
it.event.roomId ?: return@mapNotNull null
it.event.eventId ?: return@mapNotNull null
TimelineEventEntity.where(realm, it.event.roomId, it.event.eventId).findFirst()
}?.filter {
it.root?.isRootThread == true || it.root?.isThread() == true
}
}
}
}

View file

@ -122,6 +122,7 @@ class SearchResultController @Inject constructor(
.spannable(spannable.toEpoxyCharSequence())
.sender(eventAndSender.sender
?: eventAndSender.event.senderId?.let { session.getRoomMember(it, data.roomId) }?.toMatrixItem())
.threadDetails(event.threadDetails)
.listener { listener?.onItemClicked(eventAndSender.event) }
.let { result.add(it) }
}

View file

@ -18,8 +18,11 @@ package im.vector.app.features.home.room.detail.search
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
@ -29,6 +32,7 @@ import im.vector.app.core.epoxy.onClick
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.features.displayname.getBestName
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.session.threads.ThreadDetails
import org.matrix.android.sdk.api.util.MatrixItem
@EpoxyModelClass(layout = R.layout.item_search_result)
@ -38,6 +42,8 @@ abstract class SearchResultItem : VectorEpoxyModel<SearchResultItem.Holder>() {
@EpoxyAttribute var formattedDate: String? = null
@EpoxyAttribute lateinit var spannable: EpoxyCharSequence
@EpoxyAttribute var sender: MatrixItem? = null
@EpoxyAttribute var threadDetails: ThreadDetails? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null
override fun bind(holder: Holder) {
@ -48,6 +54,36 @@ abstract class SearchResultItem : VectorEpoxyModel<SearchResultItem.Holder>() {
holder.memberNameView.setTextOrHide(sender?.getBestName())
holder.timeView.text = formattedDate
holder.contentView.text = spannable.charSequence
if (BuildConfig.THREADING_ENABLED) {
threadDetails?.let {
if (it.isRootThread) {
showThreadSummary(holder)
holder.threadSummaryCounterTextView.text = it.numberOfThreads.toString()
holder.threadSummaryInfoTextView.text = it.threadSummaryLatestTextMessage.orEmpty()
val userId = it.threadSummarySenderInfo?.userId ?: return@let
val displayName = it.threadSummarySenderInfo?.displayName
val avatarUrl = it.threadSummarySenderInfo?.avatarUrl
avatarRenderer.render(MatrixItem.UserItem(userId, displayName, avatarUrl), holder.threadSummaryAvatarImageView)
} else {
showFromThread(holder)
}
} ?: run {
holder.threadSummaryConstraintLayout.isVisible = false
holder.fromThreadConstraintLayout.isVisible = false
}
}
}
private fun showThreadSummary(holder: Holder, show: Boolean = true) {
holder.threadSummaryConstraintLayout.isVisible = show
holder.fromThreadConstraintLayout.isVisible = !show
}
private fun showFromThread(holder: Holder, show: Boolean = true) {
holder.threadSummaryConstraintLayout.isVisible = !show
holder.fromThreadConstraintLayout.isVisible = show
}
class Holder : VectorEpoxyHolder() {
@ -55,5 +91,10 @@ abstract class SearchResultItem : VectorEpoxyModel<SearchResultItem.Holder>() {
val memberNameView by bind<TextView>(R.id.messageMemberNameView)
val timeView by bind<TextView>(R.id.messageTimeView)
val contentView by bind<TextView>(R.id.messageContentView)
val threadSummaryConstraintLayout by bind<ConstraintLayout>(R.id.searchThreadSummaryConstraintLayout)
val threadSummaryCounterTextView by bind<TextView>(R.id.messageThreadSummaryCounterTextView)
val threadSummaryAvatarImageView by bind<ImageView>(R.id.messageThreadSummaryAvatarImageView)
val threadSummaryInfoTextView by bind<TextView>(R.id.messageThreadSummaryInfoTextView)
val fromThreadConstraintLayout by bind<ConstraintLayout>(R.id.searchFromThreadConstraintLayout)
}
}

View file

@ -62,4 +62,66 @@
app:layout_constraintTop_toBottomOf="@id/messageMemberNameView"
tools:text="@sample/messages.json/data/message" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/searchThreadSummaryConstraintLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constrainedWidth="true"
android:layout_below="@id/informationBottom"
app:layout_constraintHorizontal_bias="0"
android:layout_toEndOf="@id/messageStartGuideline"
android:background="@drawable/rounded_rect_shape_8"
android:contentDescription="@string/room_threads_filter"
android:maxWidth="496dp"
android:minWidth="144dp"
android:paddingStart="13dp"
android:paddingTop="8dp"
android:paddingEnd="13dp"
android:paddingBottom="10dp"
android:visibility="gone"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="@id/messageContentView"
app:layout_constraintStart_toStartOf="@id/messageContentView"
app:layout_constraintTop_toBottomOf="@id/messageContentView"
tools:visibility="gone">
<include layout="@layout/view_thread_room_summary" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/searchFromThreadConstraintLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="12dp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/messageContentView"
app:layout_constraintTop_toBottomOf="@id/messageContentView"
tools:visibility="visible">
<ImageView
android:id="@+id/searchFromThreadImageView"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginTop="2dp"
android:contentDescription="@string/room_threads_filter"
android:src="@drawable/ic_thread_summary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/searchFromThreadTextView"
style="@style/Widget.Vector.TextView.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:minEms="1"
android:textColor="?vctr_content_secondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/searchFromThreadImageView"
app:layout_constraintTop_toTopOf="parent"
android:text="@string/search_thread_from_a_thread" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1049,6 +1049,7 @@
<string name="thread_list_empty_subtitle">Threads help keep your conversations on-topic and easy to track.</string>
<!-- Parameter %s will be replaced by the value of string reply_in_thread -->
<string name="thread_list_empty_notice">Tip: Long tap a message and use “%s”.</string>
<string name="search_thread_from_a_thread">From a Thread</string>
<!-- Room events -->
<string name="room_event_action_report_prompt_reason">Reason for reporting this content</string>