Unified seach: allow querying different providers asynchronously, and get their IDs in the result

First step towards getting "Load more" working for a single provider

Signed-off-by: Álvaro Brey Vilas <alvaro.brey@nextcloud.com>
This commit is contained in:
Álvaro Brey Vilas 2021-09-15 12:40:54 +02:00
parent b0b9d3a3e3
commit 6c98b4cb8d
No known key found for this signature in database
GPG key ID: 2585783189A62105
5 changed files with 81 additions and 37 deletions

View file

@ -145,10 +145,10 @@ class UnifiedSearchFragment : Fragment(), Injectable, UnifiedSearchListInterface
}
@VisibleForTesting
fun onSearchResultChanged(list: MutableList<SearchResult>) {
fun onSearchResultChanged(result: MutableMap<String, SearchResult>) {
binding.emptyList.emptyListView.visibility = View.GONE
adapter.setList(list)
adapter.setList(result.values.toList())
}
@VisibleForTesting

View file

@ -24,10 +24,17 @@ package com.owncloud.android.ui.unifiedsearch
import com.owncloud.android.lib.common.SearchResult
data class UnifiedSearchResults(val success: Boolean, val results: List<SearchResult>)
typealias ProviderID = String
data class UnifiedSearchResult(val provider: ProviderID, val success: Boolean, val result: SearchResult)
interface IUnifiedSearchRepository {
fun refresh()
fun startLoading()
fun loadMore(query: String, onResult: (UnifiedSearchResults) -> Unit, onError: (Throwable) -> Unit)
fun queryAll(
query: String,
onResult: (UnifiedSearchResult) -> Unit,
onError: (Throwable) -> Unit,
onFinished: (Boolean) -> Unit
)
}

View file

@ -22,6 +22,7 @@ package com.owncloud.android.ui.unifiedsearch
import com.nextcloud.android.lib.resources.search.UnifiedSearchRemoteOperation
import com.nextcloud.common.NextcloudClient
import com.owncloud.android.lib.common.SearchResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.utils.Log_OC
class SearchOnProvidersTask(
@ -34,24 +35,27 @@ class SearchOnProvidersTask(
const val TAG = "SearchOnProvidersTask"
}
data class Result(val success: Boolean = false, val searchResults: List<SearchResult> = emptyList())
data class Result(val success: Boolean = false, val searchResults: Map<ProviderID, SearchResult> = emptyMap())
override fun invoke(): Result {
Log_OC.d(TAG, "Run task")
val results = providers
val results: List<Pair<String, RemoteOperationResult<SearchResult>>> = providers
.map { UnifiedSearchRemoteOperation(it, query) }
.map { it.execute(client) }
.map { Pair(it.provider, it.execute(client)) }
val success = results.isNotEmpty() && results.any { it.isSuccess }
val success = results.isNotEmpty() && results.any { it.second.isSuccess }
Log_OC.d(TAG, "Task finished, success: $success")
Log_OC.d(TAG, "Providers successful: ${results.count { it.isSuccess }}")
Log_OC.d(TAG, "Providers successful: ${results.count { !it.isSuccess }}")
Log_OC.d(TAG, "Providers successful: ${results.count { it.second.isSuccess }}")
Log_OC.d(TAG, "Providers successful: ${results.count { !it.second.isSuccess }}")
return when {
success -> Result(
success = true,
searchResults = results.filter { it.isSuccess }.map { it.resultData }
searchResults = results
.filter { it.second.isSuccess }
.map { Pair(it.first, it.second.resultData) }
.toMap()
)
else -> Result()
}

View file

@ -43,30 +43,48 @@ class UnifiedSearchRemoteRepository(
TODO("Not yet implemented")
}
override fun loadMore(
override fun queryAll(
query: String,
onResult: (UnifiedSearchResults) -> Unit,
onError: (Throwable) -> Unit
onResult: (UnifiedSearchResult) -> Unit,
onError: (Throwable) -> Unit,
onFinished: (Boolean) -> Unit
) {
Log_OC.d(this, "loadMore")
fetchProviders(
onResult = { result ->
val providerIds = result.providers.map { it.id }
var openRequests = providerIds.size
var anyError = false
val client = clientFactory.createNextcloudClient(currentAccountProvider.user)
val task = SearchOnProvidersTask(query, providerIds, client)
providerIds
.forEach { provider ->
val task = SearchOnProviderTask(query, provider, client)
asyncRunner.postQuickTask(
task = task,
onResult = {
onResult(UnifiedSearchResults(it.success, it.searchResults))
openRequests--
anyError = anyError || !it.success
onResult(UnifiedSearchResult(provider, it.success, it.searchResult))
if (openRequests == 0) {
onFinished(!anyError)
}
},
onError = onError
onError = {
openRequests--
anyError = true
onError(it)
if (openRequests == 0) {
onFinished(!anyError)
}
}
)
}
},
onError = onError
)
}
private fun fetchProviders(onResult: (SearchProviders) -> Unit, onError: (Throwable) -> Unit) {
fun fetchProviders(onResult: (SearchProviders) -> Unit, onError: (Throwable) -> Unit) {
Log_OC.d(this, "fetchProviders")
if (this.providers != null) {
onResult(this.providers!!)

View file

@ -47,10 +47,14 @@ class UnifiedSearchViewModel() : ViewModel() {
private var last: Int = -1
val isLoading: MutableLiveData<Boolean> = MutableLiveData<Boolean>(false)
val searchResults = MutableLiveData<MutableList<SearchResult>>(mutableListOf())
val searchResults = MutableLiveData<MutableMap<ProviderID, SearchResult>>(mutableMapOf())
val error: MutableLiveData<String> = MutableLiveData<String>("")
val query: MutableLiveData<String> = MutableLiveData()
companion object {
private const val TAG = "UnifiedSearchViewModel"
}
@Inject
constructor(
currentAccountProvider: CurrentAccountProvider,
@ -74,27 +78,31 @@ class UnifiedSearchViewModel() : ViewModel() {
open fun refresh() {
last = -1
searchResults.value = mutableListOf()
loadMore()
searchResults.value = mutableMapOf()
startLoading(query.value.orEmpty())
}
open fun startLoading(query: String) {
if (!loadingStarted) {
loadingStarted = true
this.query.value = query
loadMore()
queryAll()
}
}
open fun loadMore() {
fun queryAll() {
val queryTerm = query.value.orEmpty()
if (isLoading.value != true && queryTerm.isNotBlank()) {
isLoading.value = true
repository.loadMore(queryTerm, this::onSearchResult, this::onError)
repository.queryAll(queryTerm, this::onSearchResult, this::onError, this::onSearchFinished)
}
}
open fun loadMore() {
// TODO load more results for a single provider
}
fun openFile(fileUrl: String) {
if (isLoading.value == false) {
isLoading.value = true
@ -115,23 +123,30 @@ class UnifiedSearchViewModel() : ViewModel() {
}
fun onError(error: Throwable) {
Log_OC.d("Unified Search", "Error: " + error.stackTrace)
Log_OC.d(TAG, "Error: " + error.stackTrace)
}
fun onSearchResult(result: UnifiedSearchResults) {
@Synchronized
fun onSearchResult(result: UnifiedSearchResult) {
isLoading.value = false
if (result.success) {
// TODO append if already exists
searchResults.value = result.results.toMutableList()
} else {
error.value = resources.getString(R.string.search_error)
val currentValues: MutableMap<ProviderID, SearchResult> = searchResults.value ?: mutableMapOf()
currentValues.put(result.provider, result.result)
searchResults.value = currentValues
}
Log_OC.d("Unified Search", "Success: " + result.success)
Log_OC.d(TAG, "onSearchResult: Provider '${result.provider}', success: ${result.success}")
if (result.success) {
Log_OC.d("Unified Search", "Got results from ${result.results.size} providers")
Log_OC.d("Unified Search", "Total results: " + result.results.sumOf { it.entries.size })
Log_OC.d(TAG, "onSearchResult: Provider '${result.provider}', result count: ${result.result.entries.size}")
}
}
fun onSearchFinished(success: Boolean) {
isLoading.value = false
if (!success) {
error.value = resources.getString(R.string.search_error)
}
}