Use sender data return from search result

This commit is contained in:
Benoit Marty 2020-10-01 17:03:53 +02:00
parent 4649b2ac1d
commit 3705fa14bd
11 changed files with 69 additions and 37 deletions

View File

@ -83,6 +83,7 @@ interface Room :
* @param beforeLimit how many events before the result are returned. * @param beforeLimit how many events before the result are returned.
* @param afterLimit how many events after the result are returned. * @param afterLimit how many events after the result are returned.
* @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned. * @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned.
* @param callback Callback to get the search result
*/ */
fun search(searchTerm: String, fun search(searchTerm: String,
nextBatch: String?, nextBatch: String?,

View File

@ -18,6 +18,7 @@
package org.matrix.android.sdk.api.session.search package org.matrix.android.sdk.api.session.search
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.util.MatrixItem
/** /**
* Domain class to represent the response of a search request in a room. * Domain class to represent the response of a search request in a room.
@ -35,5 +36,10 @@ data class SearchResult(
/** /**
* List of results in the requested order. * List of results in the requested order.
*/ */
val results: List<Event>? = null val results: List<EventAndSender>? = null
)
data class EventAndSender(
val event: Event,
val sender: MatrixItem.UserItem?
) )

View File

@ -36,6 +36,7 @@ interface SearchService {
* @param beforeLimit how many events before the result are returned. * @param beforeLimit how many events before the result are returned.
* @param afterLimit how many events after the result are returned. * @param afterLimit how many events after the result are returned.
* @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned. * @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned.
* @param callback Callback to get the search result
*/ */
fun search(searchTerm: String, fun search(searchTerm: String,
roomId: String, roomId: String,

View File

@ -18,7 +18,9 @@
package org.matrix.android.sdk.internal.session.search package org.matrix.android.sdk.internal.session.search
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.session.search.EventAndSender
import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.api.session.search.SearchResult
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.search.request.SearchRequestBody import org.matrix.android.sdk.internal.session.search.request.SearchRequestBody
import org.matrix.android.sdk.internal.session.search.request.SearchRequestCategories import org.matrix.android.sdk.internal.session.search.request.SearchRequestCategories
@ -76,7 +78,21 @@ internal class DefaultSearchTask @Inject constructor(
return SearchResult( return SearchResult(
nextBatch = searchCategories.roomEvents?.nextBatch, nextBatch = searchCategories.roomEvents?.nextBatch,
highlights = searchCategories.roomEvents?.highlights, highlights = searchCategories.roomEvents?.highlights,
results = searchCategories.roomEvents?.results?.map { it.event }?.reversed() results = searchCategories.roomEvents?.results?.map { searchResponseItem ->
EventAndSender(
searchResponseItem.event,
searchResponseItem.event.senderId?.let { senderId ->
searchResponseItem.context?.profileInfo?.get(senderId)
?.let {
MatrixItem.UserItem(
senderId,
it["displayname"] as? String,
it["avatar_url"] as? String
)
}
}
)
}?.reversed()
) )
} }
} }

View File

@ -38,5 +38,5 @@ internal data class SearchResponseEventContext(
val end: String? = null, val end: String? = null,
// The historic profile information of the users that sent the events returned. The string key is the user ID for which the profile belongs to. // The historic profile information of the users that sent the events returned. The string key is the user ID for which the profile belongs to.
@Json(name = "profile_info") @Json(name = "profile_info")
val profileInfo: JsonDict? = null val profileInfo: Map<String, JsonDict>? = null
) )

View File

@ -70,7 +70,8 @@ class SearchActivity : VectorBaseActivity() {
fun newIntent(context: Context, args: SearchArgs): Intent { fun newIntent(context: Context, args: SearchArgs): Intent {
return Intent(context, SearchActivity::class.java).apply { return Intent(context, SearchActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT // If we do that we will have the same room two times on the stack. Let's allow infinite stack for the moment.
// flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
putExtra(MvRx.KEY_ARG, args) putExtra(MvRx.KEY_ARG, args)
} }
} }

View File

@ -100,8 +100,7 @@ class SearchFragment @Inject constructor(
} }
} }
} else { } else {
val lastBatchSize = state.lastBatch?.size ?: 0 pendingScrollToPosition = (state.lastBatchSize - 1).coerceAtLeast(0)
pendingScrollToPosition = if (lastBatchSize > 0) lastBatchSize - 1 else 0
stateView.state = StateView.State.Content stateView.state = StateView.State.Content
controller.setData(state) controller.setData(state)

View File

@ -25,6 +25,8 @@ import im.vector.app.core.ui.list.genericItemHeader
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.search.EventAndSender
import org.matrix.android.sdk.api.util.toMatrixItem
import java.util.Calendar import java.util.Calendar
import javax.inject.Inject import javax.inject.Inject
@ -62,15 +64,15 @@ class SearchResultController @Inject constructor(
} }
} }
buildSearchResultItems(data.searchResult.orEmpty()) buildSearchResultItems(data.searchResult)
} }
private fun buildSearchResultItems(events: List<Event>) { private fun buildSearchResultItems(events: List<EventAndSender>) {
var lastDate: Calendar? = null var lastDate: Calendar? = null
events.forEach { event -> events.forEach { eventAndSender ->
val eventDate = Calendar.getInstance().apply { val eventDate = Calendar.getInstance().apply {
timeInMillis = event.originServerTs ?: System.currentTimeMillis() timeInMillis = eventAndSender.event.originServerTs ?: System.currentTimeMillis()
} }
if (lastDate?.get(Calendar.DAY_OF_YEAR) != eventDate.get(Calendar.DAY_OF_YEAR)) { if (lastDate?.get(Calendar.DAY_OF_YEAR) != eventDate.get(Calendar.DAY_OF_YEAR)) {
genericItemHeader { genericItemHeader {
@ -81,13 +83,13 @@ class SearchResultController @Inject constructor(
lastDate = eventDate lastDate = eventDate
searchResultItem { searchResultItem {
id(event.eventId) id(eventAndSender.event.eventId)
avatarRenderer(avatarRenderer) avatarRenderer(avatarRenderer)
dateFormatter(dateFormatter) dateFormatter(dateFormatter)
event(event) event(eventAndSender.event)
// I think we should use the data returned by the server? sender(eventAndSender.sender
sender(event.senderId?.let { session.getUser(it) }) ?: eventAndSender.event.senderId?.let { session.getUser(it) }?.toMatrixItem())
listener { listener?.onItemClicked(event) } listener { listener?.onItemClicked(eventAndSender.event) }
} }
} }
} }

View File

@ -27,10 +27,10 @@ import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.onClick import im.vector.app.core.epoxy.onClick
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
@EpoxyModelClass(layout = R.layout.item_search_result) @EpoxyModelClass(layout = R.layout.item_search_result)
abstract class SearchResultItem : VectorEpoxyModel<SearchResultItem.Holder>() { abstract class SearchResultItem : VectorEpoxyModel<SearchResultItem.Holder>() {
@ -38,15 +38,15 @@ abstract class SearchResultItem : VectorEpoxyModel<SearchResultItem.Holder>() {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute var dateFormatter: VectorDateFormatter? = null @EpoxyAttribute var dateFormatter: VectorDateFormatter? = null
@EpoxyAttribute lateinit var event: Event @EpoxyAttribute lateinit var event: Event
@EpoxyAttribute var sender: User? = null @EpoxyAttribute var sender: MatrixItem? = null
@EpoxyAttribute var listener: ClickListener? = null @EpoxyAttribute var listener: ClickListener? = null
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
holder.view.onClick(listener) holder.view.onClick(listener)
sender?.toMatrixItem()?.let { avatarRenderer.render(it, holder.avatarImageView) } sender?.let { avatarRenderer.render(it, holder.avatarImageView) }
holder.memberNameView.text = sender?.getBestName() holder.memberNameView.setTextOrHide(sender?.getBestName())
holder.timeView.text = dateFormatter?.format(event.originServerTs, DateFormatKind.MESSAGE_SIMPLE) holder.timeView.text = dateFormatter?.format(event.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
// TODO Improve that (use formattedBody, etc.) // TODO Improve that (use formattedBody, etc.)
holder.contentView.text = event.content?.get("body") as? String holder.contentView.text = event.content?.get("body") as? String

View File

@ -69,9 +69,14 @@ class SearchViewModel @AssistedInject constructor(
} }
private fun handleSearchWith(action: SearchAction.SearchWith) { private fun handleSearchWith(action: SearchAction.SearchWith) {
if (action.searchTerm.length > 1) { if (action.searchTerm.isNotEmpty()) {
setState { setState {
copy(searchTerm = action.searchTerm) copy(
searchResult = emptyList(),
hasMoreResult = false,
lastBatchSize = 0,
searchTerm = action.searchTerm
)
} }
startSearching(false) startSearching(false)
} }
@ -100,9 +105,7 @@ class SearchViewModel @AssistedInject constructor(
} }
} }
if (state.asyncSearchRequest is Loading) {
currentTask?.cancel() currentTask?.cancel()
}
viewModelScope.launch { viewModelScope.launch {
try { try {
@ -118,24 +121,22 @@ class SearchViewModel @AssistedInject constructor(
callback = it callback = it
) )
} }
onSearchResultSuccess(result, isNextBatch) onSearchResultSuccess(result)
} catch (failure: Throwable) { } catch (failure: Throwable) {
if (failure is Failure.Cancelled) return@launch if (failure is Failure.Cancelled) return@launch
_viewEvents.post(SearchViewEvents.Failure(failure)) _viewEvents.post(SearchViewEvents.Failure(failure))
setState { setState {
copy( copy(
asyncSearchRequest = Fail(failure), asyncSearchRequest = Fail(failure)
searchResult = null
) )
} }
} }
} }
} }
private fun onSearchResultSuccess(searchResult: SearchResult, isNextBatch: Boolean) = withState { state -> private fun onSearchResultSuccess(searchResult: SearchResult) = withState { state ->
// Accumulate results if it is the next batch val accumulatedResult = searchResult.results.orEmpty().plus(state.searchResult)
val accumulatedResult = searchResult.results.orEmpty().plus(state.searchResult?.takeIf { isNextBatch }.orEmpty())
// Note: We do not care about the highlights for the moment, but it will be the same algorithm // Note: We do not care about the highlights for the moment, but it will be the same algorithm
@ -145,9 +146,14 @@ class SearchViewModel @AssistedInject constructor(
copy( copy(
searchResult = accumulatedResult, searchResult = accumulatedResult,
hasMoreResult = !nextBatch.isNullOrEmpty(), hasMoreResult = !nextBatch.isNullOrEmpty(),
lastBatch = searchResult.results, lastBatchSize = searchResult.results.orEmpty().size,
asyncSearchRequest = Success(Unit) asyncSearchRequest = Success(Unit)
) )
} }
} }
override fun onCleared() {
currentTask?.cancel()
super.onCleared()
}
} }

View File

@ -19,14 +19,14 @@ package im.vector.app.features.home.room.detail.search
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.search.EventAndSender
data class SearchViewState( data class SearchViewState(
// Accumulated search result // Accumulated search result
val searchResult: List<Event>? = null, val searchResult: List<EventAndSender> = emptyList(),
val hasMoreResult: Boolean = false, val hasMoreResult: Boolean = false,
// Last batch result will help RecyclerView to position itself // Last batch size, will help RecyclerView to position itself
val lastBatch: List<Event>? = null, val lastBatchSize: Int = 0,
val searchTerm: String? = null, val searchTerm: String? = null,
val roomId: String = "", val roomId: String = "",
// Current pagination request // Current pagination request