mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-07 23:58:40 +01:00
Support searching and pagination.
This commit is contained in:
parent
5e56e7cf82
commit
62449ee543
@ -19,12 +19,24 @@ package im.vector.app.features.home.room.detail.search
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.trackItemsVisibilityChange
|
||||
import im.vector.app.core.platform.StateView
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import timber.log.Timber
|
||||
import kotlinx.android.synthetic.main.fragment_search.*
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import javax.inject.Inject
|
||||
|
||||
@Parcelize
|
||||
@ -33,19 +45,94 @@ data class SearchArgs(
|
||||
) : Parcelable
|
||||
|
||||
class SearchFragment @Inject constructor(
|
||||
val viewModelFactory: SearchViewModel.Factory
|
||||
) : VectorBaseFragment() {
|
||||
val viewModelFactory: SearchViewModel.Factory,
|
||||
val controller: SearchResultController,
|
||||
val stringProvider: StringProvider
|
||||
) : VectorBaseFragment(), StateView.EventCallback, SearchResultController.Listener {
|
||||
|
||||
private val fragmentArgs: SearchArgs by args()
|
||||
private val searchViewModel: SearchViewModel by fragmentViewModel()
|
||||
|
||||
private var pendingScrollToPosition: Int? = null
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_search
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
stateView.contentView = searchResultRecycler
|
||||
stateView.eventCallback = this
|
||||
|
||||
configureRecyclerView()
|
||||
|
||||
searchViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is SearchViewEvents.Failure -> {
|
||||
stateView.state = StateView.State.Error(errorFormatter.toHumanReadable(it.throwable))
|
||||
}
|
||||
is SearchViewEvents.Loading -> {
|
||||
stateView.state = StateView.State.Loading
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureRecyclerView() {
|
||||
searchResultRecycler.trackItemsVisibilityChange()
|
||||
searchResultRecycler.configureWith(controller, showDivider = false)
|
||||
controller.listener = this
|
||||
|
||||
controller.addModelBuildListener {
|
||||
pendingScrollToPosition?.let {
|
||||
searchResultRecycler.scrollToPosition(it)
|
||||
}
|
||||
}
|
||||
|
||||
searchResultRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
// Load next batch when scrolled to the top
|
||||
if (newState == RecyclerView.SCROLL_STATE_IDLE
|
||||
&& (searchResultRecycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() == 0) {
|
||||
searchViewModel.handle(SearchAction.ScrolledToTop)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
searchResultRecycler?.cleanup()
|
||||
controller.listener = null
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(searchViewModel) { state ->
|
||||
if (state.searchResult?.results?.isNotEmpty() == true) {
|
||||
stateView.state = StateView.State.Content
|
||||
controller.setData(state)
|
||||
|
||||
val lastBatchSize = state.lastBatch?.results?.size ?: 0
|
||||
val scrollPosition = if (lastBatchSize > 0) lastBatchSize - 1 else 0
|
||||
pendingScrollToPosition = scrollPosition
|
||||
} else {
|
||||
stateView.state = StateView.State.Empty(
|
||||
title = stringProvider.getString(R.string.search_no_results),
|
||||
image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_search)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun search(query: String) {
|
||||
Timber.d(query)
|
||||
view?.hideKeyboard()
|
||||
searchViewModel.handle(SearchAction.SearchWith(fragmentArgs.roomId, query))
|
||||
}
|
||||
|
||||
override fun onRetryClicked() {
|
||||
searchViewModel.handle(SearchAction.Retry)
|
||||
}
|
||||
|
||||
override fun onItemClicked(event: Event) {
|
||||
event.roomId ?: return
|
||||
|
||||
navigator.openRoom(requireContext(), event.roomId!!, event.eventId)
|
||||
}
|
||||
}
|
||||
|
@ -21,10 +21,15 @@ import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.search.SearchResult
|
||||
|
||||
class SearchViewModel @AssistedInject constructor(
|
||||
@Assisted private val initialState: SearchViewState
|
||||
@Assisted private val initialState: SearchViewState,
|
||||
private val session: Session
|
||||
) : VectorViewModel<SearchViewState, SearchAction, SearchViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
@ -42,5 +47,90 @@ class SearchViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
override fun handle(action: SearchAction) {
|
||||
when (action) {
|
||||
is SearchAction.SearchWith -> handleSearchWith(action)
|
||||
is SearchAction.ScrolledToTop -> handleScrolledToTop()
|
||||
is SearchAction.Retry -> handleRetry()
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleSearchWith(action: SearchAction.SearchWith) {
|
||||
if (action.searchTerm.length > 1) {
|
||||
setState {
|
||||
copy(searchTerm = action.searchTerm, roomId = action.roomId, isNextBatch = false)
|
||||
}
|
||||
|
||||
startSearching()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleScrolledToTop() {
|
||||
setState {
|
||||
copy(isNextBatch = true)
|
||||
}
|
||||
startSearching(true)
|
||||
}
|
||||
|
||||
private fun handleRetry() {
|
||||
startSearching()
|
||||
}
|
||||
|
||||
private fun startSearching(scrolledToTop: Boolean = false) = withState { state ->
|
||||
if (state.roomId == null || state.searchTerm == null) return@withState
|
||||
|
||||
// There is no batch to retrieve
|
||||
if (scrolledToTop && state.searchResult?.nextBatch == null) return@withState
|
||||
|
||||
_viewEvents.post(SearchViewEvents.Loading())
|
||||
|
||||
session
|
||||
.getRoom(state.roomId)
|
||||
?.search(
|
||||
searchTerm = state.searchTerm,
|
||||
nextBatch = state.searchResult?.nextBatch,
|
||||
orderByRecent = true,
|
||||
beforeLimit = 0,
|
||||
afterLimit = 0,
|
||||
includeProfile = true,
|
||||
limit = 20,
|
||||
callback = object : MatrixCallback<SearchResult> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
onSearchFailure(failure)
|
||||
}
|
||||
|
||||
override fun onSuccess(data: SearchResult) {
|
||||
onSearchResultSuccess(data)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun onSearchFailure(failure: Throwable) {
|
||||
setState {
|
||||
copy(searchResult = null)
|
||||
}
|
||||
_viewEvents.post(SearchViewEvents.Failure(failure))
|
||||
}
|
||||
|
||||
private fun onSearchResultSuccess(searchResult: SearchResult) = withState { state ->
|
||||
val accumulatedResult = SearchResult(
|
||||
nextBatch = searchResult.nextBatch,
|
||||
results = searchResult.results,
|
||||
highlights = searchResult.highlights
|
||||
)
|
||||
|
||||
// Accumulate results if it is the next batch
|
||||
if (state.isNextBatch) {
|
||||
if (state.searchResult != null) {
|
||||
accumulatedResult.results = accumulatedResult.results?.plus(state.searchResult.results!!)
|
||||
}
|
||||
if (state.searchResult?.highlights != null) {
|
||||
accumulatedResult.highlights = accumulatedResult.highlights?.plus(state.searchResult.highlights!!)
|
||||
}
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(searchResult = accumulatedResult, lastBatch = searchResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user