Add API to control online search

This commit is contained in:
MM20 2024-01-28 21:35:36 +01:00
parent d7340c485b
commit 426473e397
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
20 changed files with 44 additions and 36 deletions

View File

@ -40,6 +40,7 @@ class SearchablePickerVM : ViewModel(), KoinComponent {
searchJob = viewModelScope.launch {
searchService.search(
query = query,
allowNetwork = true,
).collectLatest {
if (searchQuery != query) return@collectLatest
items = withContext(Dispatchers.Default) {

View File

@ -127,7 +127,8 @@ class SearchVM : ViewModel(), KoinComponent {
searchJob = viewModelScope.launch {
searchUiSettings.resultOrder.collectLatest { resultOrder ->
searchService.search(
query
query,
allowNetwork = true,
).collectLatest { results ->
var resultsList = withContext(Dispatchers.Default) {
listOfNotNull(

View File

@ -4,5 +4,5 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.Flow
interface SearchableRepository<T : Searchable> {
fun search(query: String): Flow<ImmutableList<T>>
fun search(query: String, allowNetwork: Boolean): Flow<ImmutableList<T>>
}

View File

@ -5,5 +5,6 @@ abstract class SearchPluginContract {
const val Search = "search"
const val Root = "root"
const val QueryParam = "query"
const val AllowNetworkParam = "network"
}
}

View File

@ -21,7 +21,7 @@ import kotlinx.coroutines.flow.map
class FakeAppRepository(private val context: Context, private val fakePackages: Int) : SearchableRepository<Application> {
override fun search(query: String): Flow<ImmutableList<Application>> {
override fun search(query: String, allowNetwork: Boolean): Flow<ImmutableList<Application>> {
return if (query.isEmpty()) {
buildList {
repeat(fakePackages) {

View File

@ -29,8 +29,6 @@ import org.apache.commons.text.similarity.FuzzyScore
import java.util.Locale
interface AppRepository : SearchableRepository<Application> {
override fun search(query: String): Flow<ImmutableList<Application>>
fun findMany(): Flow<ImmutableList<Application>>
}
@ -197,7 +195,7 @@ internal class AppRepositoryImpl(
return installedApps.map { it.toImmutableList() }
}
override fun search(query: String): Flow<ImmutableList<LauncherApp>> {
override fun search(query: String, allowNetwork: Boolean): Flow<ImmutableList<LauncherApp>> {
return installedApps.map { apps ->
withContext(Dispatchers.Default) {
val appResults = mutableListOf<LauncherApp>()

View File

@ -111,7 +111,7 @@ internal class AppShortcutRepositoryImpl(
return flags
}
override fun search(query: String): Flow<ImmutableList<AppShortcut>> {
override fun search(query: String, allowNetwork: Boolean): Flow<ImmutableList<AppShortcut>> {
if (query.length < 3) {
return flowOf(persistentListOf())
}

View File

@ -40,7 +40,7 @@ internal class CalendarRepositoryImpl(
private val settings: CalendarSearchSettings,
) : CalendarRepository {
override fun search(query: String): Flow<ImmutableList<CalendarEvent>> {
override fun search(query: String, allowNetwork: Boolean): Flow<ImmutableList<CalendarEvent>> {
if (query.isBlank() || query.length < 2) {
return flow {
emit(persistentListOf())

View File

@ -164,7 +164,7 @@ internal class ContactRepository(
)
}
override fun search(query: String): Flow<ImmutableList<Contact>> {
override fun search(query: String, allowNetwork: Boolean): Flow<ImmutableList<Contact>> {
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Contacts)
if (query.length < 2) {

View File

@ -32,6 +32,7 @@ internal class FileRepository(
override fun search(
query: String,
allowNetwork: Boolean,
) = channelFlow {
if (query.isBlank()) {
send(persistentListOf())
@ -55,7 +56,7 @@ internal class FileRepository(
}
val results = mutableListOf<File>()
for (provider in providers) {
results.addAll(provider.search(query))
results.addAll(provider.search(query, allowNetwork))
send(results.toImmutableList())
}
}

View File

@ -3,5 +3,5 @@ package de.mm20.launcher2.files.providers
import de.mm20.launcher2.search.File
internal interface FileProvider {
suspend fun search(query: String): List<File>
suspend fun search(query: String, allowNetwork: Boolean): List<File>
}

View File

@ -12,8 +12,8 @@ import kotlinx.collections.immutable.toImmutableMap
internal class GDriveFileProvider(
private val context: Context
) : FileProvider {
override suspend fun search(query: String): List<File> {
if (query.length < 4) return emptyList()
override suspend fun search(query: String, allowNetwork: Boolean): List<File> {
if (query.length < 4 || !allowNetwork) return emptyList()
val driveFiles = GoogleApiHelper.getInstance(context).queryGDriveFiles(query)
return driveFiles.map {
GDriveFile(

View File

@ -13,7 +13,7 @@ internal class LocalFileProvider(
private val context: Context,
private val permissionsManager: PermissionsManager
): FileProvider {
override suspend fun search(query: String): List<File> = withContext(Dispatchers.IO) {
override suspend fun search(query: String, allowNetwork: Boolean): List<File> = withContext(Dispatchers.IO) {
if (!permissionsManager.checkPermissionOnce(PermissionGroup.ExternalStorage)) {
return@withContext emptyList()
}

View File

@ -12,8 +12,8 @@ import kotlin.math.min
internal class NextcloudFileProvider(
private val nextcloudClient: NextcloudApiHelper
) : FileProvider {
override suspend fun search(query: String): List<File> {
if (query.length < 4) return emptyList()
override suspend fun search(query: String, allowNetwork: Boolean): List<File> {
if (query.length < 4 || !allowNetwork) return emptyList()
val server = nextcloudClient.getServer() ?: return emptyList()
return withContext(Dispatchers.IO) {
nextcloudClient.files.search(query).let { it.subList(0, min(10, it.size)) }.map {

View File

@ -9,8 +9,8 @@ import kotlinx.collections.immutable.persistentMapOf
internal class OwncloudFileProvider(
private val owncloudClient: OwncloudClient
) : FileProvider {
override suspend fun search(query: String): List<File> {
if (query.length < 4) return emptyList()
override suspend fun search(query: String, allowNetwork: Boolean): List<File> {
if (query.length < 4 || !allowNetwork) return emptyList()
val server = owncloudClient.getServer() ?: return emptyList()
return owncloudClient.files.query(query).map {
OwncloudFile(

View File

@ -26,12 +26,13 @@ class PluginFileProvider(
private val context: Context,
private val pluginAuthority: String,
) : FileProvider {
override suspend fun search(query: String): List<File> = withContext(Dispatchers.IO) {
override suspend fun search(query: String, allowNetwork: Boolean): List<File> = withContext(Dispatchers.IO) {
val uri = Uri.Builder()
.scheme("content")
.authority(pluginAuthority)
.path(SearchPluginContract.Paths.Search)
.appendQueryParameter(SearchPluginContract.Paths.QueryParam, query)
.appendQueryParameter(SearchPluginContract.Paths.AllowNetworkParam, allowNetwork.toString())
.build()
val cancellationSignal = CancellationSignal()

View File

@ -2,7 +2,6 @@ package de.mm20.launcher2.websites
import android.content.Context
import android.webkit.URLUtil
import androidx.compose.runtime.Immutable
import androidx.core.graphics.toColorInt
import de.mm20.launcher2.preferences.search.WebsiteSearchSettings
import de.mm20.launcher2.search.SearchableRepository
@ -11,8 +10,7 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl
@ -20,7 +18,6 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import org.jsoup.Jsoup
import org.jsoup.UncheckedIOException
import org.koin.core.component.KoinComponent
import java.io.IOException
import java.net.MalformedURLException
import java.net.URISyntaxException
@ -40,8 +37,9 @@ internal class WebsiteRepository(
.writeTimeout(1000, TimeUnit.MILLISECONDS)
.build()
override fun search(query: String): Flow<ImmutableList<Website>> {
return settings.enabled.transformLatest {enabled ->
override fun search(query: String, allowNetwork: Boolean): Flow<ImmutableList<Website>> {
if (!allowNetwork) return flowOf(persistentListOf())
return settings.enabled.transformLatest { enabled ->
emit(persistentListOf())
withContext(Dispatchers.IO) {
httpClient.dispatcher.cancelAll()

View File

@ -53,8 +53,8 @@ internal class WikipediaRepository(
private lateinit var wikipediaService: WikipediaApi
override fun search(query: String): Flow<ImmutableList<Wikipedia>> {
if (query.length < 4) return flowOf(persistentListOf())
override fun search(query: String, allowNetwork: Boolean): Flow<ImmutableList<Wikipedia>> {
if (query.length < 4 || !allowNetwork) return flowOf(persistentListOf())
return settings.enabled.transformLatest {
emit(persistentListOf())

View File

@ -19,7 +19,7 @@ abstract class SearchPluginProvider<T>(
* Search for items matching the given query
* @param query The query to search for
*/
abstract suspend fun search(query: String): List<T>
abstract suspend fun search(query: String, allowNetwork: Boolean): List<T>
abstract suspend fun get(id: String): T?
override fun onCreate(): Boolean {
@ -48,7 +48,10 @@ abstract class SearchPluginProvider<T>(
uri.pathSegments.size == 1 && uri.pathSegments.first() == SearchPluginContract.Paths.Search -> {
val query =
uri.getQueryParameter(SearchPluginContract.Paths.QueryParam) ?: return null
val results = search(query, cancellationSignal)
val allowNetwork =
uri.getQueryParameter(SearchPluginContract.Paths.AllowNetworkParam)?.toBoolean()
?: false
val results = search(query, allowNetwork, cancellationSignal)
val cursor = createCursor(results.size)
for (result in results) {
writeToCursor(cursor, result)
@ -95,10 +98,11 @@ abstract class SearchPluginProvider<T>(
private fun search(
query: String,
allowNetwork: Boolean,
cancellationSignal: CancellationSignal?
): List<T> {
return launchWithCancellationSignal(cancellationSignal) {
search(query)
search(query, allowNetwork)
}
}

View File

@ -23,6 +23,7 @@ import kotlinx.coroutines.supervisorScope
interface SearchService {
fun search(
query: String,
allowNetwork: Boolean = false,
): Flow<SearchResults>
}
@ -42,6 +43,7 @@ internal class SearchServiceImpl(
override fun search(
query: String,
allowNetwork: Boolean,
): Flow<SearchResults> = channelFlow {
val results = MutableStateFlow(SearchResults())
supervisorScope {
@ -54,7 +56,7 @@ internal class SearchServiceImpl(
}
}
launch {
appRepository.search(query)
appRepository.search(query, allowNetwork)
.withCustomLabels(customAttributesRepository)
.collectLatest { r ->
results.update {
@ -63,7 +65,7 @@ internal class SearchServiceImpl(
}
}
launch {
appShortcutRepository.search(query)
appShortcutRepository.search(query, allowNetwork)
.withCustomLabels(customAttributesRepository)
.collectLatest { r ->
results.update {
@ -72,7 +74,7 @@ internal class SearchServiceImpl(
}
}
launch {
contactRepository.search(query)
contactRepository.search(query, allowNetwork)
.withCustomLabels(customAttributesRepository)
.collectLatest { r ->
results.update {
@ -81,7 +83,7 @@ internal class SearchServiceImpl(
}
}
launch {
calendarRepository.search(query)
calendarRepository.search(query, allowNetwork)
.withCustomLabels(customAttributesRepository)
.collectLatest { r ->
results.update {
@ -107,7 +109,7 @@ internal class SearchServiceImpl(
}
}
launch {
websiteRepository.search(query)
websiteRepository.search(query, allowNetwork)
.withCustomLabels(customAttributesRepository)
.collectLatest { r ->
results.update {
@ -117,7 +119,7 @@ internal class SearchServiceImpl(
}
launch {
delay(750)
articleRepository.search(query)
articleRepository.search(query, allowNetwork)
.withCustomLabels(customAttributesRepository)
.collectLatest { r ->
results.update {
@ -128,6 +130,7 @@ internal class SearchServiceImpl(
launch {
fileRepository.search(
query,
allowNetwork
)
.withCustomLabels(customAttributesRepository)
.collectLatest { r ->