Optimizations

This commit is contained in:
MM20 2023-02-19 23:39:14 +01:00
parent 722652e982
commit e5fe3a8816
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
7 changed files with 160 additions and 121 deletions

View File

@ -1,14 +1,11 @@
package de.mm20.launcher2.ui.launcher.search package de.mm20.launcher2.ui.launcher.search
import android.content.Context import android.content.Context
import android.util.Log
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.map
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.permissions.PermissionGroup 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.search.data.Wikipedia
import de.mm20.launcher2.searchactions.actions.SearchAction import de.mm20.launcher2.searchactions.actions.SearchAction
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
@ -111,7 +106,6 @@ class SearchVM : ViewModel(), KoinComponent {
} }
hideFavorites.postValue(query.isNotEmpty()) hideFavorites.postValue(query.isNotEmpty())
searchJob = viewModelScope.launch { searchJob = viewModelScope.launch {
dataStore.data.collectLatest { dataStore.data.collectLatest {
searchService.search( searchService.search(
query, query,
@ -124,6 +118,22 @@ class SearchVM : ViewModel(), KoinComponent {
websites = it.websiteSearch, websites = it.websiteSearch,
wikipedia = it.wikipediaSearch, wikipedia = it.wikipediaSearch,
).collectLatest { results -> ).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 -> hiddenItemKeys.collectLatest { hiddenKeys ->
val hidden = mutableListOf<SavableSearchable>() val hidden = mutableListOf<SavableSearchable>()
val apps = mutableListOf<LauncherApp>() val apps = mutableListOf<LauncherApp>()
@ -137,12 +147,11 @@ class SearchVM : ViewModel(), KoinComponent {
val wikipedia = mutableListOf<Wikipedia>() val wikipedia = mutableListOf<Wikipedia>()
val website = mutableListOf<Website>() val website = mutableListOf<Website>()
val actions = mutableListOf<SearchAction>() val actions = mutableListOf<SearchAction>()
for (r in results) { for (r in resultsList) {
when { when {
r is SavableSearchable && hiddenKeys.contains(r.key) -> { r is SavableSearchable && hiddenKeys.contains(r.key) -> {
hidden.add(r) hidden.add(r)
} }
r is LauncherApp && !r.isMainProfile -> workApps.add(r) r is LauncherApp && !r.isMainProfile -> workApps.add(r)
r is LauncherApp -> apps.add(r) r is LauncherApp -> apps.add(r)
r is AppShortcut -> shortcuts.add(r) r is AppShortcut -> shortcuts.add(r)
@ -156,6 +165,7 @@ class SearchVM : ViewModel(), KoinComponent {
r is SearchAction -> actions.add(r) r is SearchAction -> actions.add(r)
} }
} }
if (query.isNotEmpty() && launchOnEnter.value) { if (query.isNotEmpty() && launchOnEnter.value) {
bestMatch.value = listOf( bestMatch.value = listOf(
apps, apps,
@ -170,7 +180,6 @@ class SearchVM : ViewModel(), KoinComponent {
).firstNotNullOfOrNull { it.firstOrNull() } ).firstNotNullOfOrNull { it.firstOrNull() }
} }
searchActionResults.value = actions
appResults.value = apps appResults.value = apps
workAppResults.value = workApps workAppResults.value = workApps
appShortcutResults.value = shortcuts appShortcutResults.value = shortcuts
@ -182,6 +191,7 @@ class SearchVM : ViewModel(), KoinComponent {
calculatorResults.value = calc calculatorResults.value = calc
unitConverterResults.value = unitConv unitConverterResults.value = unitConv
hiddenResults.value = hidden hiddenResults.value = hidden
if (results.searchActions != null) searchActionResults.value = actions
} }
} }
} }

View File

@ -33,9 +33,7 @@ class HiddenItemsSettingsScreenVM : ViewModel(), KoinComponent {
withContext(Dispatchers.Default) { it.sorted() } withContext(Dispatchers.Default) { it.sorted() }
}.asLiveData() }.asLiveData()
val hiddenItems: LiveData<List<SavableSearchable>> = liveData { val hiddenItems: LiveData<List<SavableSearchable>> = liveData {
val hidden = withContext(Dispatchers.Default) { val hidden = favoritesRepository.getHiddenItems().first().filter { it !is LauncherApp }.sorted()
favoritesRepository.getHiddenItems().first().filter { it !is LauncherApp }.sorted()
}
emit(hidden) emit(hidden)
} }

View File

@ -21,7 +21,6 @@ import de.mm20.launcher2.searchactions.builders.WebsearchActionBuilder
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
import java.io.File import java.io.File
@ -51,15 +50,13 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
} }
fun getSearchableApps(context: Context) = flow { fun getSearchableApps(context: Context) = flow {
val items = withContext(Dispatchers.Default) { val items = searchActionService.getSearchActivities().map {
searchActionService.getSearchActivities().map { SearchableApp(
SearchableApp( label = context.packageManager.getActivityInfo(it, 0)
label = context.packageManager.getActivityInfo(it, 0) .loadLabel(context.packageManager).toString(),
.loadLabel(context.packageManager).toString(), componentName = it
componentName = it )
) }.sortedBy { it.label.romanize().lowercase() }
}.sortedBy { it.label.romanize().lowercase() }
}
emit(items) emit(items)
} }
@ -122,6 +119,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
is AppSearchActionBuilder -> action.also { is AppSearchActionBuilder -> action.also {
it.baseIntent.setComponent(componentName) it.baseIntent.setComponent(componentName)
} }
is WebsearchActionBuilder -> action is WebsearchActionBuilder -> action
} }
@ -130,6 +128,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
val initWebsearchUrl = mutableStateOf("") val initWebsearchUrl = mutableStateOf("")
/** /**
* Last imported URL that failed (if the current URL is equal to this, show an error banner) * 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 } derivedStateOf { websearchImportErrorUrl.value == initWebsearchUrl.value }
val loadingWebsearch = mutableStateOf(false) val loadingWebsearch = mutableStateOf(false)
val skipWebsearchImport = derivedStateOf { websearchImportError.value || initWebsearchUrl.value.isEmpty() } val skipWebsearchImport =
derivedStateOf { websearchImportError.value || initWebsearchUrl.value.isEmpty() }
fun importWebsearch(density: Density) { fun importWebsearch(density: Density) {
if (loadingWebsearch.value) return if (loadingWebsearch.value) return
@ -180,14 +180,15 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
private val invalidWebsearchUrl = mutableStateOf<String?>(null) private val invalidWebsearchUrl = mutableStateOf<String?>(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) val customIntentKeyError = mutableStateOf(false)
fun validate() : Boolean { fun validate(): Boolean {
val action = searchAction.value ?: return false val action = searchAction.value ?: return false
if (action is WebsearchActionBuilder) { if (action is WebsearchActionBuilder) {
val valid = action.urlTemplate.contains("\${1}") val valid = action.urlTemplate.contains("\${1}")
invalidWebsearchUrl.value = if(valid) null else action.urlTemplate invalidWebsearchUrl.value = if (valid) null else action.urlTemplate
return valid return valid
} }
if (action is CustomIntentActionBuilder) { if (action is CustomIntentActionBuilder) {
@ -216,9 +217,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
if (action.customIcon != initialCustomIcon) { if (action.customIcon != initialCustomIcon) {
deleteCustomIcon(action.customIcon) deleteCustomIcon(action.customIcon)
} }
searchAction.value = when(action) { searchAction.value = when (action) {
is WebsearchActionBuilder -> action.copy(icon = icon, customIcon = null, iconColor = 0) 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) is AppSearchActionBuilder -> action.copy(icon = icon, customIcon = null, iconColor = 0)
} }
} }
@ -228,10 +234,24 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
if (action.customIcon != initialCustomIcon) { if (action.customIcon != initialCustomIcon) {
deleteCustomIcon(action.customIcon) deleteCustomIcon(action.customIcon)
} }
searchAction.value = when(action) { searchAction.value = when (action) {
is WebsearchActionBuilder -> action.copy(customIcon = iconPath, iconColor = 1, icon = SearchActionIcon.Custom) is WebsearchActionBuilder -> action.copy(
is CustomIntentActionBuilder -> action.copy(customIcon = iconPath, iconColor = 1, icon = SearchActionIcon.Custom) customIcon = iconPath,
is AppSearchActionBuilder -> action.copy(customIcon = iconPath, iconColor = 1, icon = SearchActionIcon.Custom) 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() { fun applyIcon() {
currentPage.value = when(searchAction.value) { currentPage.value = when (searchAction.value) {
is AppSearchActionBuilder -> EditSearchActionPage.CustomizeAppSearch is AppSearchActionBuilder -> EditSearchActionPage.CustomizeAppSearch
is WebsearchActionBuilder -> EditSearchActionPage.CustomizeWebSearch is WebsearchActionBuilder -> EditSearchActionPage.CustomizeWebSearch
is CustomIntentActionBuilder -> EditSearchActionPage.CustomizeCustomIntent is CustomIntentActionBuilder -> EditSearchActionPage.CustomizeCustomIntent
@ -257,7 +277,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
fun setIconColor(color: Int) { fun setIconColor(color: Int) {
val action = searchAction.value ?: return val action = searchAction.value ?: return
searchAction.value = when(action) { searchAction.value = when (action) {
is WebsearchActionBuilder -> action.copy(iconColor = color) is WebsearchActionBuilder -> action.copy(iconColor = color)
is CustomIntentActionBuilder -> action.copy(iconColor = color) is CustomIntentActionBuilder -> action.copy(iconColor = color)
is AppSearchActionBuilder -> action.copy(iconColor = color) is AppSearchActionBuilder -> action.copy(iconColor = color)
@ -273,7 +293,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
fun removeExtra(key: String) { fun removeExtra(key: String) {
val action = searchAction.value ?: return val action = searchAction.value ?: return
searchAction.value = when(action) { searchAction.value = when (action) {
is CustomIntentActionBuilder -> action.copy( is CustomIntentActionBuilder -> action.copy(
baseIntent = action.baseIntent.cloneFilter().also { baseIntent = action.baseIntent.cloneFilter().also {
val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() val extras = action.baseIntent.extras?.deepCopy() ?: Bundle()
@ -281,6 +301,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
it.replaceExtras(extras) it.replaceExtras(extras)
}, },
) )
is AppSearchActionBuilder -> action.copy( is AppSearchActionBuilder -> action.copy(
baseIntent = action.baseIntent.cloneFilter().also { baseIntent = action.baseIntent.cloneFilter().also {
val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() val extras = action.baseIntent.extras?.deepCopy() ?: Bundle()
@ -288,13 +309,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
it.replaceExtras(extras) it.replaceExtras(extras)
} }
) )
else -> action else -> action
} }
} }
fun putStringExtra(key: String, value: String = "") { fun putStringExtra(key: String, value: String = "") {
val action = searchAction.value ?: return val action = searchAction.value ?: return
searchAction.value = when(action) { searchAction.value = when (action) {
is CustomIntentActionBuilder -> action.copy( is CustomIntentActionBuilder -> action.copy(
baseIntent = action.baseIntent.cloneFilter().also { baseIntent = action.baseIntent.cloneFilter().also {
val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() val extras = action.baseIntent.extras?.deepCopy() ?: Bundle()
@ -302,6 +324,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
it.replaceExtras(extras) it.replaceExtras(extras)
}, },
) )
is AppSearchActionBuilder -> action.copy( is AppSearchActionBuilder -> action.copy(
baseIntent = action.baseIntent.cloneFilter().also { baseIntent = action.baseIntent.cloneFilter().also {
val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() val extras = action.baseIntent.extras?.deepCopy() ?: Bundle()
@ -309,13 +332,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
it.replaceExtras(extras) it.replaceExtras(extras)
}, },
) )
else -> action else -> action
} }
} }
fun putIntExtra(key: String, value: Int = 0) { fun putIntExtra(key: String, value: Int = 0) {
val action = searchAction.value ?: return val action = searchAction.value ?: return
searchAction.value = when(action) { searchAction.value = when (action) {
is CustomIntentActionBuilder -> action.copy( is CustomIntentActionBuilder -> action.copy(
baseIntent = action.baseIntent.cloneFilter().also { baseIntent = action.baseIntent.cloneFilter().also {
val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() val extras = action.baseIntent.extras?.deepCopy() ?: Bundle()
@ -323,6 +347,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
it.replaceExtras(extras) it.replaceExtras(extras)
}, },
) )
is AppSearchActionBuilder -> action.copy( is AppSearchActionBuilder -> action.copy(
baseIntent = action.baseIntent.cloneFilter().also { baseIntent = action.baseIntent.cloneFilter().also {
val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() val extras = action.baseIntent.extras?.deepCopy() ?: Bundle()
@ -330,13 +355,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
it.replaceExtras(extras) it.replaceExtras(extras)
}, },
) )
else -> action else -> action
} }
} }
fun putLongExtra(key: String, value: Long = 0L) { fun putLongExtra(key: String, value: Long = 0L) {
val action = searchAction.value ?: return val action = searchAction.value ?: return
searchAction.value = when(action) { searchAction.value = when (action) {
is CustomIntentActionBuilder -> action.copy( is CustomIntentActionBuilder -> action.copy(
baseIntent = action.baseIntent.cloneFilter().also { baseIntent = action.baseIntent.cloneFilter().also {
val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() val extras = action.baseIntent.extras?.deepCopy() ?: Bundle()
@ -344,6 +370,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
it.replaceExtras(extras) it.replaceExtras(extras)
}, },
) )
is AppSearchActionBuilder -> action.copy( is AppSearchActionBuilder -> action.copy(
baseIntent = action.baseIntent.cloneFilter().also { baseIntent = action.baseIntent.cloneFilter().also {
val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() val extras = action.baseIntent.extras?.deepCopy() ?: Bundle()
@ -351,13 +378,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
it.replaceExtras(extras) it.replaceExtras(extras)
}, },
) )
else -> action else -> action
} }
} }
fun putFloatExtra(key: String, value: Float = 0f) { fun putFloatExtra(key: String, value: Float = 0f) {
val action = searchAction.value ?: return val action = searchAction.value ?: return
searchAction.value = when(action) { searchAction.value = when (action) {
is CustomIntentActionBuilder -> action.copy( is CustomIntentActionBuilder -> action.copy(
baseIntent = action.baseIntent.cloneFilter().also { baseIntent = action.baseIntent.cloneFilter().also {
val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() val extras = action.baseIntent.extras?.deepCopy() ?: Bundle()
@ -365,6 +393,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
it.replaceExtras(extras) it.replaceExtras(extras)
}, },
) )
is AppSearchActionBuilder -> action.copy( is AppSearchActionBuilder -> action.copy(
baseIntent = action.baseIntent.cloneFilter().also { baseIntent = action.baseIntent.cloneFilter().also {
val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() val extras = action.baseIntent.extras?.deepCopy() ?: Bundle()
@ -372,13 +401,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
it.replaceExtras(extras) it.replaceExtras(extras)
}, },
) )
else -> action else -> action
} }
} }
fun putDoubleExtra(key: String, value: Double = 0.0) { fun putDoubleExtra(key: String, value: Double = 0.0) {
val action = searchAction.value ?: return val action = searchAction.value ?: return
searchAction.value = when(action) { searchAction.value = when (action) {
is CustomIntentActionBuilder -> action.copy( is CustomIntentActionBuilder -> action.copy(
baseIntent = action.baseIntent.cloneFilter().also { baseIntent = action.baseIntent.cloneFilter().also {
val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() val extras = action.baseIntent.extras?.deepCopy() ?: Bundle()
@ -386,6 +416,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
it.replaceExtras(extras) it.replaceExtras(extras)
}, },
) )
is AppSearchActionBuilder -> action.copy( is AppSearchActionBuilder -> action.copy(
baseIntent = action.baseIntent.cloneFilter().also { baseIntent = action.baseIntent.cloneFilter().also {
val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() val extras = action.baseIntent.extras?.deepCopy() ?: Bundle()
@ -393,13 +424,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
it.replaceExtras(extras) it.replaceExtras(extras)
}, },
) )
else -> action else -> action
} }
} }
fun putBooleanExtra(key: String, value: Boolean = false) { fun putBooleanExtra(key: String, value: Boolean = false) {
val action = searchAction.value ?: return val action = searchAction.value ?: return
searchAction.value = when(action) { searchAction.value = when (action) {
is CustomIntentActionBuilder -> action.copy( is CustomIntentActionBuilder -> action.copy(
baseIntent = action.baseIntent.cloneFilter().also { baseIntent = action.baseIntent.cloneFilter().also {
val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() val extras = action.baseIntent.extras?.deepCopy() ?: Bundle()
@ -407,6 +439,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
it.replaceExtras(extras) it.replaceExtras(extras)
}, },
) )
is AppSearchActionBuilder -> action.copy( is AppSearchActionBuilder -> action.copy(
baseIntent = action.baseIntent.cloneFilter().also { baseIntent = action.baseIntent.cloneFilter().also {
val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() val extras = action.baseIntent.extras?.deepCopy() ?: Bundle()
@ -414,13 +447,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
it.replaceExtras(extras) it.replaceExtras(extras)
}, },
) )
else -> action else -> action
} }
} }
fun setIntentAction(action: String) { fun setIntentAction(action: String) {
val searchAction = searchAction.value ?: return val searchAction = searchAction.value ?: return
this.searchAction.value = when(searchAction) { this.searchAction.value = when (searchAction) {
is CustomIntentActionBuilder -> searchAction.copy( is CustomIntentActionBuilder -> searchAction.copy(
baseIntent = searchAction.baseIntent.cloneFilter().also { baseIntent = searchAction.baseIntent.cloneFilter().also {
val extras = searchAction.baseIntent.extras?.deepCopy() ?: Bundle() val extras = searchAction.baseIntent.extras?.deepCopy() ?: Bundle()
@ -428,13 +462,14 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
it.action = action it.action = action
}, },
) )
else -> searchAction else -> searchAction
} }
} }
fun setIntentCategory(category: String) { fun setIntentCategory(category: String) {
val action = searchAction.value ?: return val action = searchAction.value ?: return
searchAction.value = when(action) { searchAction.value = when (action) {
is CustomIntentActionBuilder -> action.copy( is CustomIntentActionBuilder -> action.copy(
baseIntent = action.baseIntent.cloneFilter().also { baseIntent = action.baseIntent.cloneFilter().also {
val extras = action.baseIntent.extras?.deepCopy() ?: Bundle() val extras = action.baseIntent.extras?.deepCopy() ?: Bundle()
@ -444,26 +479,29 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
if (category.isNotBlank()) it.addCategory(category) if (category.isNotBlank()) it.addCategory(category)
}, },
) )
else -> action else -> action
} }
} }
fun setIntentQueryExtra(key: String) { fun setIntentQueryExtra(key: String) {
val action = searchAction.value ?: return val action = searchAction.value ?: return
searchAction.value = when(action) { searchAction.value = when (action) {
is CustomIntentActionBuilder -> action.copy( is CustomIntentActionBuilder -> action.copy(
queryKey = key, queryKey = key,
) )
else -> action else -> action
} }
} }
fun setQueryEncoding(encoding: WebsearchActionBuilder.QueryEncoding) { fun setQueryEncoding(encoding: WebsearchActionBuilder.QueryEncoding) {
val action = searchAction.value ?: return val action = searchAction.value ?: return
searchAction.value = when(action) { searchAction.value = when (action) {
is WebsearchActionBuilder -> action.copy( is WebsearchActionBuilder -> action.copy(
encoding = encoding encoding = encoding
) )
else -> action else -> action
} }
} }

View File

@ -16,10 +16,12 @@ import de.mm20.launcher2.search.data.LauncherApp
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers 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 kotlinx.coroutines.withContext
import org.apache.commons.text.similarity.FuzzyScore import org.apache.commons.text.similarity.FuzzyScore
import java.util.* import java.util.Locale
interface AppRepository { interface AppRepository {
fun getAllInstalledApps(): Flow<List<LauncherApp>> fun getAllInstalledApps(): Flow<List<LauncherApp>>
@ -141,9 +143,8 @@ internal class AppRepositoryImpl(
return LauncherApp(context, launcherActivityInfo) return LauncherApp(context, launcherActivityInfo)
} }
override fun search(query: String): Flow<ImmutableList<LauncherApp>> = channelFlow { override fun search(query: String): Flow<ImmutableList<LauncherApp>> {
return installedApps.map { apps ->
installedApps.collectLatest { apps ->
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
val appResults = mutableListOf<LauncherApp>() val appResults = mutableListOf<LauncherApp>()
if (query.isEmpty()) { if (query.isEmpty()) {
@ -156,10 +157,8 @@ internal class AppRepositoryImpl(
val componentName = ComponentName.unflattenFromString(query) val componentName = ComponentName.unflattenFromString(query)
getActivityByComponentName(componentName)?.let { appResults.add(it) } getActivityByComponentName(componentName)?.let { appResults.add(it) }
} }
appResults.sort() appResults.sort()
appResults.toImmutableList()
send(appResults.toImmutableList())
} }
} }
} }

View File

@ -21,8 +21,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -37,7 +36,7 @@ import java.net.URL
import java.util.UUID import java.util.UUID
interface SearchActionService { interface SearchActionService {
fun search(query: String): Flow<ImmutableList<SearchAction>> suspend fun search(query: String): Flow<ImmutableList<SearchAction>>
fun getSearchActionBuilders(): Flow<List<SearchActionBuilder>> fun getSearchActionBuilders(): Flow<List<SearchActionBuilder>>
fun getDisabledActionBuilders(): Flow<List<SearchActionBuilder>> fun getDisabledActionBuilders(): Flow<List<SearchActionBuilder>>
@ -56,23 +55,21 @@ internal class SearchActionServiceImpl(
private val repository: SearchActionRepository, private val repository: SearchActionRepository,
private val textClassifier: TextClassifier, private val textClassifier: TextClassifier,
) : SearchActionService { ) : SearchActionService {
override fun search( override suspend fun search(
query: String query: String
): Flow<ImmutableList<SearchAction>> = flow { ): Flow<ImmutableList<SearchAction>> {
if (query.isBlank()) { if (query.isBlank()) {
emit(persistentListOf()) return flowOf(persistentListOf())
return@flow
} }
val classificationResult = textClassifier.classify(context, query) val classificationResult = textClassifier.classify(context, query)
val builders = repository.getSearchActionBuilders() val builders = repository.getSearchActionBuilders()
emitAll( return builders.map {
builders.map { it.mapNotNull { it.build(context, classificationResult) }.toImmutableList()
it.mapNotNull { it.build(context, classificationResult) }.toImmutableList() }
}
)
} }
override fun getSearchActionBuilders(): Flow<List<SearchActionBuilder>> { override fun getSearchActionBuilders(): Flow<List<SearchActionBuilder>> {
@ -136,7 +133,10 @@ internal class SearchActionServiceImpl(
return@withContext null return@withContext null
} }
private suspend fun importOpenSearch(openSearchHref: String, iconSize: Int): WebsearchActionBuilder? { private suspend fun importOpenSearch(
openSearchHref: String,
iconSize: Int
): WebsearchActionBuilder? {
try { try {
val httpClient = OkHttpClient() val httpClient = OkHttpClient()
val request = Request.Builder() val request = Request.Builder()
@ -217,7 +217,8 @@ internal class SearchActionServiceImpl(
} }
} catch (e: IOException) { } catch (e: IOException) {
} catch (e: XmlPullParserException) {} } catch (e: XmlPullParserException) {
}
return null return null
} }
@ -242,7 +243,8 @@ internal class SearchActionServiceImpl(
override suspend fun getSearchActivities(): List<ComponentName> { override suspend fun getSearchActivities(): List<ComponentName> {
return withContext(Dispatchers.Default) { return withContext(Dispatchers.Default) {
val resolveInfos = context.packageManager.queryIntentActivities( 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 { resolveInfos.mapNotNull {
if (!it.activityInfo.exported || !it.activityInfo.enabled) return@mapNotNull null if (!it.activityInfo.exported || !it.activityInfo.enabled) return@mapNotNull null

View File

@ -51,7 +51,7 @@ class IconPackManager(
} }
suspend fun updateIconPacks() { suspend fun updateIconPacks() {
withContext(Dispatchers.Default) { withContext(Dispatchers.IO) {
IconPackInstaller(context, appDatabase).installIcons() IconPackInstaller(context, appDatabase).installIcons()
GrayscaleMapInstaller(context, appDatabase).installIcons() GrayscaleMapInstaller(context, appDatabase).installIcons()
} }

View File

@ -30,8 +30,8 @@ import de.mm20.launcher2.search.data.OwncloudFile
import de.mm20.launcher2.search.data.UnitConverter import de.mm20.launcher2.search.data.UnitConverter
import de.mm20.launcher2.search.data.Website import de.mm20.launcher2.search.data.Website
import de.mm20.launcher2.search.data.Wikipedia 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.SearchActionService
import de.mm20.launcher2.searchactions.actions.SearchAction
import de.mm20.launcher2.unitconverter.UnitConverterRepository import de.mm20.launcher2.unitconverter.UnitConverterRepository
import de.mm20.launcher2.websites.WebsiteRepository import de.mm20.launcher2.websites.WebsiteRepository
import de.mm20.launcher2.wikipedia.WikipediaRepository import de.mm20.launcher2.wikipedia.WikipediaRepository
@ -58,7 +58,7 @@ interface SearchService {
unitConverter: UnitConverterSearchSettings, unitConverter: UnitConverterSearchSettings,
websites: WebsiteSearchSettings, websites: WebsiteSearchSettings,
wikipedia: WikipediaSearchSettings, wikipedia: WikipediaSearchSettings,
): Flow<ImmutableList<Searchable>> ): Flow<SearchResults>
} }
internal class SearchServiceImpl( internal class SearchServiceImpl(
@ -85,16 +85,23 @@ internal class SearchServiceImpl(
unitConverter: UnitConverterSearchSettings, unitConverter: UnitConverterSearchSettings,
websites: WebsiteSearchSettings, websites: WebsiteSearchSettings,
wikipedia: WikipediaSearchSettings, wikipedia: WikipediaSearchSettings,
): Flow<ImmutableList<Searchable>> = channelFlow { ): Flow<SearchResults> = channelFlow {
var searchActionsReady = false val results = MutableStateFlow(SearchResults())
supervisorScope { supervisorScope {
val results = MutableStateFlow(SearchResults()) launch {
searchActionService.search(query)
.collectLatest { r ->
results.update {
it.copy(searchActions = r)
}
}
}
launch { launch {
appRepository.search(query) appRepository.search(query)
.withCustomLabels(customAttributesRepository) .withCustomLabels(customAttributesRepository)
.collectLatest { r -> .collectLatest { r ->
results.update { results.update {
it.copy(apps = r) it.copy(apps = r.toImmutableList())
} }
} }
} }
@ -104,7 +111,7 @@ internal class SearchServiceImpl(
.withCustomLabels(customAttributesRepository) .withCustomLabels(customAttributesRepository)
.collectLatest { r -> .collectLatest { r ->
results.update { results.update {
it.copy(shortcuts = r) it.copy(shortcuts = r.toImmutableList())
} }
} }
} }
@ -115,7 +122,7 @@ internal class SearchServiceImpl(
.withCustomLabels(customAttributesRepository) .withCustomLabels(customAttributesRepository)
.collectLatest { r -> .collectLatest { r ->
results.update { results.update {
it.copy(contacts = r) it.copy(contacts = r.toImmutableList())
} }
} }
} }
@ -126,7 +133,7 @@ internal class SearchServiceImpl(
.withCustomLabels(customAttributesRepository) .withCustomLabels(customAttributesRepository)
.collectLatest { r -> .collectLatest { r ->
results.update { results.update {
it.copy(calendars = r) it.copy(calendars = r.toImmutableList())
} }
} }
} }
@ -143,12 +150,13 @@ internal class SearchServiceImpl(
} }
if (unitConverter.enabled) { if (unitConverter.enabled) {
launch { launch {
unitConverterRepository.search(query, unitConverter.currencies).collectLatest { r -> unitConverterRepository.search(query, unitConverter.currencies)
results.update { .collectLatest { r ->
it.copy(unitConverters = r?.let { persistentListOf(it) } results.update {
?: persistentListOf()) it.copy(unitConverters = r?.let { persistentListOf(it) }
?: persistentListOf())
}
} }
}
} }
} }
if (websites.enabled) { if (websites.enabled) {
@ -158,7 +166,7 @@ internal class SearchServiceImpl(
.withCustomLabels(customAttributesRepository) .withCustomLabels(customAttributesRepository)
.collectLatest { r -> .collectLatest { r ->
results.update { results.update {
it.copy(websites = r) it.copy(websites = r.toImmutableList())
} }
} }
} }
@ -170,7 +178,7 @@ internal class SearchServiceImpl(
.withCustomLabels(customAttributesRepository) .withCustomLabels(customAttributesRepository)
.collectLatest { r -> .collectLatest { r ->
results.update { results.update {
it.copy(wikipedia = r) it.copy(wikipedia = r.toImmutableList())
} }
} }
} }
@ -188,7 +196,7 @@ internal class SearchServiceImpl(
.withCustomLabels(customAttributesRepository) .withCustomLabels(customAttributesRepository)
.collectLatest { r -> .collectLatest { r ->
results.update { results.update {
it.copy(files = r) it.copy(files = r.toImmutableList())
} }
} }
} }
@ -218,39 +226,23 @@ internal class SearchServiceImpl(
} }
} }
launch { launch {
searchActionService.search(query) results.collectLatest { send(it) }
.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)
}
} }
} }
} }
} }
internal data class SearchResults( data class SearchResults(
val apps: List<LauncherApp> = emptyList(), val apps: ImmutableList<LauncherApp>? = null,
val shortcuts: List<AppShortcut> = emptyList(), val shortcuts: ImmutableList<AppShortcut>? = null,
val contacts: List<Contact> = emptyList(), val contacts: ImmutableList<Contact>? = null,
val calendars: List<CalendarEvent> = emptyList(), val calendars: ImmutableList<CalendarEvent>? = null,
val files: List<File> = emptyList(), val files: ImmutableList<File>? = null,
val calculators: List<Calculator> = emptyList(), val calculators: ImmutableList<Calculator>? = null,
val unitConverters: List<UnitConverter> = emptyList(), val unitConverters: ImmutableList<UnitConverter>? = null,
val websites: List<Website> = emptyList(), val websites: ImmutableList<Website>? = null,
val wikipedia: List<Wikipedia> = emptyList(), val wikipedia: ImmutableList<Wikipedia>? = null,
val searchActions: List<SearchAction> = emptyList(), val searchActions: ImmutableList<SearchAction>? = null,
val other: List<SavableSearchable> = emptyList(), val other: ImmutableList<SavableSearchable>? = null,
) { )
fun toList(): List<Searchable> {
return searchActions + (apps + shortcuts + contacts + calendars + files + websites + wikipedia + other).distinctBy { it.key } + calculators + unitConverters
}
}