diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt index 8f72bf9e..52a59f2d 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt @@ -1,14 +1,11 @@ package de.mm20.launcher2.ui.launcher.search import android.content.Context +import android.util.Log import androidx.appcompat.app.AppCompatActivity -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.asLiveData -import androidx.lifecycle.map import androidx.lifecycle.viewModelScope import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.permissions.PermissionGroup @@ -28,20 +25,18 @@ import de.mm20.launcher2.search.data.Website import de.mm20.launcher2.search.data.Wikipedia import de.mm20.launcher2.searchactions.actions.SearchAction import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapLatest -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -111,7 +106,6 @@ class SearchVM : ViewModel(), KoinComponent { } hideFavorites.postValue(query.isNotEmpty()) searchJob = viewModelScope.launch { - dataStore.data.collectLatest { searchService.search( query, @@ -124,6 +118,22 @@ class SearchVM : ViewModel(), KoinComponent { websites = it.websiteSearch, wikipedia = it.wikipediaSearch, ).collectLatest { results -> + val resultsList = withContext(Dispatchers.Default) { + listOfNotNull( + results.apps, + results.other, + results.shortcuts, + results.files, + results.contacts, + results.calendars, + results.wikipedia, + results.websites, + results.calculators, + results.unitConverters, + results.searchActions, + ).flatten().sortedBy { (it as? SavableSearchable)?.label } + } + hiddenItemKeys.collectLatest { hiddenKeys -> val hidden = mutableListOf() val apps = mutableListOf() @@ -137,12 +147,11 @@ class SearchVM : ViewModel(), KoinComponent { val wikipedia = mutableListOf() val website = mutableListOf() val actions = mutableListOf() - for (r in results) { + for (r in resultsList) { when { r is SavableSearchable && hiddenKeys.contains(r.key) -> { hidden.add(r) } - r is LauncherApp && !r.isMainProfile -> workApps.add(r) r is LauncherApp -> apps.add(r) r is AppShortcut -> shortcuts.add(r) @@ -156,6 +165,7 @@ class SearchVM : ViewModel(), KoinComponent { r is SearchAction -> actions.add(r) } } + if (query.isNotEmpty() && launchOnEnter.value) { bestMatch.value = listOf( apps, @@ -170,7 +180,6 @@ class SearchVM : ViewModel(), KoinComponent { ).firstNotNullOfOrNull { it.firstOrNull() } } - searchActionResults.value = actions appResults.value = apps workAppResults.value = workApps appShortcutResults.value = shortcuts @@ -182,6 +191,7 @@ class SearchVM : ViewModel(), KoinComponent { calculatorResults.value = calc unitConverterResults.value = unitConv hiddenResults.value = hidden + if (results.searchActions != null) searchActionResults.value = actions } } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreenVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreenVM.kt index f6502a90..798a7d7a 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreenVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreenVM.kt @@ -33,9 +33,7 @@ class HiddenItemsSettingsScreenVM : ViewModel(), KoinComponent { withContext(Dispatchers.Default) { it.sorted() } }.asLiveData() val hiddenItems: LiveData> = liveData { - val hidden = withContext(Dispatchers.Default) { - favoritesRepository.getHiddenItems().first().filter { it !is LauncherApp }.sorted() - } + val hidden = favoritesRepository.getHiddenItems().first().filter { it !is LauncherApp }.sorted() emit(hidden) } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/searchactions/EditSearchActionSheetVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/searchactions/EditSearchActionSheetVM.kt index b627a303..b9e84cd2 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/searchactions/EditSearchActionSheetVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/searchactions/EditSearchActionSheetVM.kt @@ -21,7 +21,6 @@ import de.mm20.launcher2.searchactions.builders.WebsearchActionBuilder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.io.File @@ -51,15 +50,13 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { } fun getSearchableApps(context: Context) = flow { - val items = withContext(Dispatchers.Default) { - searchActionService.getSearchActivities().map { - SearchableApp( - label = context.packageManager.getActivityInfo(it, 0) - .loadLabel(context.packageManager).toString(), - componentName = it - ) - }.sortedBy { it.label.romanize().lowercase() } - } + val items = searchActionService.getSearchActivities().map { + SearchableApp( + label = context.packageManager.getActivityInfo(it, 0) + .loadLabel(context.packageManager).toString(), + componentName = it + ) + }.sortedBy { it.label.romanize().lowercase() } emit(items) } @@ -122,6 +119,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { is AppSearchActionBuilder -> action.also { it.baseIntent.setComponent(componentName) } + is WebsearchActionBuilder -> action } @@ -130,6 +128,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { val initWebsearchUrl = mutableStateOf("") + /** * Last imported URL that failed (if the current URL is equal to this, show an error banner) */ @@ -138,7 +137,8 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { derivedStateOf { websearchImportErrorUrl.value == initWebsearchUrl.value } val loadingWebsearch = mutableStateOf(false) - val skipWebsearchImport = derivedStateOf { websearchImportError.value || initWebsearchUrl.value.isEmpty() } + val skipWebsearchImport = + derivedStateOf { websearchImportError.value || initWebsearchUrl.value.isEmpty() } fun importWebsearch(density: Density) { if (loadingWebsearch.value) return @@ -180,14 +180,15 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { private val invalidWebsearchUrl = mutableStateOf(null) - val websearchInvalidUrlError = derivedStateOf { invalidWebsearchUrl.value == (searchAction.value as? WebsearchActionBuilder)?.urlTemplate } + val websearchInvalidUrlError = + derivedStateOf { invalidWebsearchUrl.value == (searchAction.value as? WebsearchActionBuilder)?.urlTemplate } val customIntentKeyError = mutableStateOf(false) - fun validate() : Boolean { + fun validate(): Boolean { val action = searchAction.value ?: return false if (action is WebsearchActionBuilder) { val valid = action.urlTemplate.contains("\${1}") - invalidWebsearchUrl.value = if(valid) null else action.urlTemplate + invalidWebsearchUrl.value = if (valid) null else action.urlTemplate return valid } if (action is CustomIntentActionBuilder) { @@ -216,9 +217,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { if (action.customIcon != initialCustomIcon) { deleteCustomIcon(action.customIcon) } - searchAction.value = when(action) { + searchAction.value = when (action) { is WebsearchActionBuilder -> action.copy(icon = icon, customIcon = null, iconColor = 0) - is CustomIntentActionBuilder -> action.copy(icon = icon, customIcon = null, iconColor = 0) + is CustomIntentActionBuilder -> action.copy( + icon = icon, + customIcon = null, + iconColor = 0 + ) + is AppSearchActionBuilder -> action.copy(icon = icon, customIcon = null, iconColor = 0) } } @@ -228,10 +234,24 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { if (action.customIcon != initialCustomIcon) { deleteCustomIcon(action.customIcon) } - searchAction.value = when(action) { - is WebsearchActionBuilder -> action.copy(customIcon = iconPath, iconColor = 1, icon = SearchActionIcon.Custom) - is CustomIntentActionBuilder -> action.copy(customIcon = iconPath, iconColor = 1, icon = SearchActionIcon.Custom) - is AppSearchActionBuilder -> action.copy(customIcon = iconPath, iconColor = 1, icon = SearchActionIcon.Custom) + searchAction.value = when (action) { + is WebsearchActionBuilder -> action.copy( + customIcon = iconPath, + iconColor = 1, + icon = SearchActionIcon.Custom + ) + + is CustomIntentActionBuilder -> action.copy( + customIcon = iconPath, + iconColor = 1, + icon = SearchActionIcon.Custom + ) + + is AppSearchActionBuilder -> action.copy( + customIcon = iconPath, + iconColor = 1, + icon = SearchActionIcon.Custom + ) } } @@ -247,7 +267,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { } fun applyIcon() { - currentPage.value = when(searchAction.value) { + currentPage.value = when (searchAction.value) { is AppSearchActionBuilder -> EditSearchActionPage.CustomizeAppSearch is WebsearchActionBuilder -> EditSearchActionPage.CustomizeWebSearch is CustomIntentActionBuilder -> EditSearchActionPage.CustomizeCustomIntent @@ -257,7 +277,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { fun setIconColor(color: Int) { val action = searchAction.value ?: return - searchAction.value = when(action) { + searchAction.value = when (action) { is WebsearchActionBuilder -> action.copy(iconColor = color) is CustomIntentActionBuilder -> action.copy(iconColor = color) is AppSearchActionBuilder -> action.copy(iconColor = color) @@ -273,7 +293,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { fun removeExtra(key: String) { val action = searchAction.value ?: return - searchAction.value = when(action) { + searchAction.value = when (action) { is CustomIntentActionBuilder -> action.copy( baseIntent = action.baseIntent.cloneFilter().also { val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() @@ -281,6 +301,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { it.replaceExtras(extras) }, ) + is AppSearchActionBuilder -> action.copy( baseIntent = action.baseIntent.cloneFilter().also { val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() @@ -288,13 +309,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { it.replaceExtras(extras) } ) + else -> action } } fun putStringExtra(key: String, value: String = "") { val action = searchAction.value ?: return - searchAction.value = when(action) { + searchAction.value = when (action) { is CustomIntentActionBuilder -> action.copy( baseIntent = action.baseIntent.cloneFilter().also { val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() @@ -302,6 +324,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { it.replaceExtras(extras) }, ) + is AppSearchActionBuilder -> action.copy( baseIntent = action.baseIntent.cloneFilter().also { val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() @@ -309,13 +332,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { it.replaceExtras(extras) }, ) + else -> action } } fun putIntExtra(key: String, value: Int = 0) { val action = searchAction.value ?: return - searchAction.value = when(action) { + searchAction.value = when (action) { is CustomIntentActionBuilder -> action.copy( baseIntent = action.baseIntent.cloneFilter().also { val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() @@ -323,6 +347,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { it.replaceExtras(extras) }, ) + is AppSearchActionBuilder -> action.copy( baseIntent = action.baseIntent.cloneFilter().also { val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() @@ -330,13 +355,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { it.replaceExtras(extras) }, ) + else -> action } } fun putLongExtra(key: String, value: Long = 0L) { val action = searchAction.value ?: return - searchAction.value = when(action) { + searchAction.value = when (action) { is CustomIntentActionBuilder -> action.copy( baseIntent = action.baseIntent.cloneFilter().also { val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() @@ -344,6 +370,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { it.replaceExtras(extras) }, ) + is AppSearchActionBuilder -> action.copy( baseIntent = action.baseIntent.cloneFilter().also { val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() @@ -351,13 +378,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { it.replaceExtras(extras) }, ) + else -> action } } fun putFloatExtra(key: String, value: Float = 0f) { val action = searchAction.value ?: return - searchAction.value = when(action) { + searchAction.value = when (action) { is CustomIntentActionBuilder -> action.copy( baseIntent = action.baseIntent.cloneFilter().also { val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() @@ -365,6 +393,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { it.replaceExtras(extras) }, ) + is AppSearchActionBuilder -> action.copy( baseIntent = action.baseIntent.cloneFilter().also { val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() @@ -372,13 +401,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { it.replaceExtras(extras) }, ) + else -> action } } fun putDoubleExtra(key: String, value: Double = 0.0) { val action = searchAction.value ?: return - searchAction.value = when(action) { + searchAction.value = when (action) { is CustomIntentActionBuilder -> action.copy( baseIntent = action.baseIntent.cloneFilter().also { val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() @@ -386,6 +416,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { it.replaceExtras(extras) }, ) + is AppSearchActionBuilder -> action.copy( baseIntent = action.baseIntent.cloneFilter().also { val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() @@ -393,13 +424,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { it.replaceExtras(extras) }, ) + else -> action } } fun putBooleanExtra(key: String, value: Boolean = false) { val action = searchAction.value ?: return - searchAction.value = when(action) { + searchAction.value = when (action) { is CustomIntentActionBuilder -> action.copy( baseIntent = action.baseIntent.cloneFilter().also { val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() @@ -407,6 +439,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { it.replaceExtras(extras) }, ) + is AppSearchActionBuilder -> action.copy( baseIntent = action.baseIntent.cloneFilter().also { val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() @@ -414,13 +447,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { it.replaceExtras(extras) }, ) + else -> action } } fun setIntentAction(action: String) { val searchAction = searchAction.value ?: return - this.searchAction.value = when(searchAction) { + this.searchAction.value = when (searchAction) { is CustomIntentActionBuilder -> searchAction.copy( baseIntent = searchAction.baseIntent.cloneFilter().also { val extras = searchAction.baseIntent.extras?.deepCopy() ?: Bundle() @@ -428,13 +462,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { it.action = action }, ) + else -> searchAction } } fun setIntentCategory(category: String) { val action = searchAction.value ?: return - searchAction.value = when(action) { + searchAction.value = when (action) { is CustomIntentActionBuilder -> action.copy( baseIntent = action.baseIntent.cloneFilter().also { val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() @@ -444,26 +479,29 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent { if (category.isNotBlank()) it.addCategory(category) }, ) + else -> action } } fun setIntentQueryExtra(key: String) { val action = searchAction.value ?: return - searchAction.value = when(action) { + searchAction.value = when (action) { is CustomIntentActionBuilder -> action.copy( queryKey = key, ) + else -> action } } fun setQueryEncoding(encoding: WebsearchActionBuilder.QueryEncoding) { val action = searchAction.value ?: return - searchAction.value = when(action) { + searchAction.value = when (action) { is WebsearchActionBuilder -> action.copy( encoding = encoding ) + else -> action } } diff --git a/data/applications/src/main/java/de/mm20/launcher2/applications/AppRepository.kt b/data/applications/src/main/java/de/mm20/launcher2/applications/AppRepository.kt index 28328a06..5e063c39 100644 --- a/data/applications/src/main/java/de/mm20/launcher2/applications/AppRepository.kt +++ b/data/applications/src/main/java/de/mm20/launcher2/applications/AppRepository.kt @@ -16,10 +16,12 @@ import de.mm20.launcher2.search.data.LauncherApp import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext import org.apache.commons.text.similarity.FuzzyScore -import java.util.* +import java.util.Locale interface AppRepository { fun getAllInstalledApps(): Flow> @@ -141,9 +143,8 @@ internal class AppRepositoryImpl( return LauncherApp(context, launcherActivityInfo) } - override fun search(query: String): Flow> = channelFlow { - - installedApps.collectLatest { apps -> + override fun search(query: String): Flow> { + return installedApps.map { apps -> withContext(Dispatchers.Default) { val appResults = mutableListOf() if (query.isEmpty()) { @@ -156,10 +157,8 @@ internal class AppRepositoryImpl( val componentName = ComponentName.unflattenFromString(query) getActivityByComponentName(componentName)?.let { appResults.add(it) } } - appResults.sort() - - send(appResults.toImmutableList()) + appResults.toImmutableList() } } } diff --git a/data/search-actions/src/main/java/de/mm20/launcher2/searchactions/SearchActionService.kt b/data/search-actions/src/main/java/de/mm20/launcher2/searchactions/SearchActionService.kt index 53db91e2..ed2090ab 100644 --- a/data/search-actions/src/main/java/de/mm20/launcher2/searchactions/SearchActionService.kt +++ b/data/search-actions/src/main/java/de/mm20/launcher2/searchactions/SearchActionService.kt @@ -21,8 +21,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext import okhttp3.OkHttpClient @@ -37,7 +36,7 @@ import java.net.URL import java.util.UUID interface SearchActionService { - fun search(query: String): Flow> + suspend fun search(query: String): Flow> fun getSearchActionBuilders(): Flow> fun getDisabledActionBuilders(): Flow> @@ -56,23 +55,21 @@ internal class SearchActionServiceImpl( private val repository: SearchActionRepository, private val textClassifier: TextClassifier, ) : SearchActionService { - override fun search( + override suspend fun search( query: String - ): Flow> = flow { + ): Flow> { + if (query.isBlank()) { - emit(persistentListOf()) - return@flow + return flowOf(persistentListOf()) } val classificationResult = textClassifier.classify(context, query) val builders = repository.getSearchActionBuilders() - emitAll( - builders.map { - it.mapNotNull { it.build(context, classificationResult) }.toImmutableList() - } - ) + return builders.map { + it.mapNotNull { it.build(context, classificationResult) }.toImmutableList() + } } override fun getSearchActionBuilders(): Flow> { @@ -136,7 +133,10 @@ internal class SearchActionServiceImpl( return@withContext null } - private suspend fun importOpenSearch(openSearchHref: String, iconSize: Int): WebsearchActionBuilder? { + private suspend fun importOpenSearch( + openSearchHref: String, + iconSize: Int + ): WebsearchActionBuilder? { try { val httpClient = OkHttpClient() val request = Request.Builder() @@ -217,7 +217,8 @@ internal class SearchActionServiceImpl( } } catch (e: IOException) { - } catch (e: XmlPullParserException) {} + } catch (e: XmlPullParserException) { + } return null } @@ -242,7 +243,8 @@ internal class SearchActionServiceImpl( override suspend fun getSearchActivities(): List { return withContext(Dispatchers.Default) { val resolveInfos = context.packageManager.queryIntentActivities( - Intent(Intent.ACTION_SEARCH).addCategory(Intent.CATEGORY_DEFAULT), PackageManager.GET_META_DATA, + Intent(Intent.ACTION_SEARCH).addCategory(Intent.CATEGORY_DEFAULT), + PackageManager.GET_META_DATA, ) resolveInfos.mapNotNull { if (!it.activityInfo.exported || !it.activityInfo.enabled) return@mapNotNull null diff --git a/services/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt b/services/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt index 9c85a692..d6fc8203 100644 --- a/services/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt +++ b/services/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt @@ -51,7 +51,7 @@ class IconPackManager( } suspend fun updateIconPacks() { - withContext(Dispatchers.Default) { + withContext(Dispatchers.IO) { IconPackInstaller(context, appDatabase).installIcons() GrayscaleMapInstaller(context, appDatabase).installIcons() } diff --git a/services/search/src/main/java/de/mm20/launcher2/search/SearchService.kt b/services/search/src/main/java/de/mm20/launcher2/search/SearchService.kt index 221575a8..720b6654 100644 --- a/services/search/src/main/java/de/mm20/launcher2/search/SearchService.kt +++ b/services/search/src/main/java/de/mm20/launcher2/search/SearchService.kt @@ -30,8 +30,8 @@ import de.mm20.launcher2.search.data.OwncloudFile import de.mm20.launcher2.search.data.UnitConverter import de.mm20.launcher2.search.data.Website import de.mm20.launcher2.search.data.Wikipedia -import de.mm20.launcher2.searchactions.actions.SearchAction import de.mm20.launcher2.searchactions.SearchActionService +import de.mm20.launcher2.searchactions.actions.SearchAction import de.mm20.launcher2.unitconverter.UnitConverterRepository import de.mm20.launcher2.websites.WebsiteRepository import de.mm20.launcher2.wikipedia.WikipediaRepository @@ -58,7 +58,7 @@ interface SearchService { unitConverter: UnitConverterSearchSettings, websites: WebsiteSearchSettings, wikipedia: WikipediaSearchSettings, - ): Flow> + ): Flow } internal class SearchServiceImpl( @@ -85,16 +85,23 @@ internal class SearchServiceImpl( unitConverter: UnitConverterSearchSettings, websites: WebsiteSearchSettings, wikipedia: WikipediaSearchSettings, - ): Flow> = channelFlow { - var searchActionsReady = false + ): Flow = channelFlow { + val results = MutableStateFlow(SearchResults()) supervisorScope { - val results = MutableStateFlow(SearchResults()) + launch { + searchActionService.search(query) + .collectLatest { r -> + results.update { + it.copy(searchActions = r) + } + } + } launch { appRepository.search(query) .withCustomLabels(customAttributesRepository) .collectLatest { r -> results.update { - it.copy(apps = r) + it.copy(apps = r.toImmutableList()) } } } @@ -104,7 +111,7 @@ internal class SearchServiceImpl( .withCustomLabels(customAttributesRepository) .collectLatest { r -> results.update { - it.copy(shortcuts = r) + it.copy(shortcuts = r.toImmutableList()) } } } @@ -115,7 +122,7 @@ internal class SearchServiceImpl( .withCustomLabels(customAttributesRepository) .collectLatest { r -> results.update { - it.copy(contacts = r) + it.copy(contacts = r.toImmutableList()) } } } @@ -126,7 +133,7 @@ internal class SearchServiceImpl( .withCustomLabels(customAttributesRepository) .collectLatest { r -> results.update { - it.copy(calendars = r) + it.copy(calendars = r.toImmutableList()) } } } @@ -143,12 +150,13 @@ internal class SearchServiceImpl( } if (unitConverter.enabled) { launch { - unitConverterRepository.search(query, unitConverter.currencies).collectLatest { r -> - results.update { - it.copy(unitConverters = r?.let { persistentListOf(it) } - ?: persistentListOf()) + unitConverterRepository.search(query, unitConverter.currencies) + .collectLatest { r -> + results.update { + it.copy(unitConverters = r?.let { persistentListOf(it) } + ?: persistentListOf()) + } } - } } } if (websites.enabled) { @@ -158,7 +166,7 @@ internal class SearchServiceImpl( .withCustomLabels(customAttributesRepository) .collectLatest { r -> results.update { - it.copy(websites = r) + it.copy(websites = r.toImmutableList()) } } } @@ -170,7 +178,7 @@ internal class SearchServiceImpl( .withCustomLabels(customAttributesRepository) .collectLatest { r -> results.update { - it.copy(wikipedia = r) + it.copy(wikipedia = r.toImmutableList()) } } } @@ -188,7 +196,7 @@ internal class SearchServiceImpl( .withCustomLabels(customAttributesRepository) .collectLatest { r -> results.update { - it.copy(files = r) + it.copy(files = r.toImmutableList()) } } } @@ -218,39 +226,23 @@ internal class SearchServiceImpl( } } launch { - searchActionService.search(query) - .collectLatest { r -> - results.update { - searchActionsReady = true - it.copy(searchActions = r) - } - } - } - launch { - results - .map { it.toList().sortedBy { it as? SavableSearchable }.toImmutableList() } - .collectLatest { - if (searchActionsReady) send(it) - } + results.collectLatest { send(it) } } + } } } -internal data class SearchResults( - val apps: List = emptyList(), - val shortcuts: List = emptyList(), - val contacts: List = emptyList(), - val calendars: List = emptyList(), - val files: List = emptyList(), - val calculators: List = emptyList(), - val unitConverters: List = emptyList(), - val websites: List = emptyList(), - val wikipedia: List = emptyList(), - val searchActions: List = emptyList(), - val other: List = emptyList(), -) { - fun toList(): List { - return searchActions + (apps + shortcuts + contacts + calendars + files + websites + wikipedia + other).distinctBy { it.key } + calculators + unitConverters - } -} \ No newline at end of file +data class SearchResults( + val apps: ImmutableList? = null, + val shortcuts: ImmutableList? = null, + val contacts: ImmutableList? = null, + val calendars: ImmutableList? = null, + val files: ImmutableList? = null, + val calculators: ImmutableList? = null, + val unitConverters: ImmutableList? = null, + val websites: ImmutableList? = null, + val wikipedia: ImmutableList? = null, + val searchActions: ImmutableList? = null, + val other: ImmutableList? = null, +) \ No newline at end of file