From c7804296d79a9755429da14c2db9358a34723f1d Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Wed, 17 Jul 2024 23:27:00 +0200 Subject: [PATCH] Refactor search --- .../launcher2/ui/launcher/search/SearchVM.kt | 338 +++++++++++------- .../launcher2/search/SearchableRepository.kt | 2 +- .../mm20/launcher2/profiles/ProfileManager.kt | 21 +- services/search/build.gradle.kts | 1 + .../java/de/mm20/launcher2/search/Module.kt | 1 + .../de/mm20/launcher2/search/SearchService.kt | 195 +++++++--- 6 files changed, 351 insertions(+), 207 deletions(-) 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 3249aea9..94422393 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,7 +1,6 @@ package de.mm20.launcher2.ui.launcher.search import android.content.Context -import android.os.Process import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel @@ -23,11 +22,11 @@ import de.mm20.launcher2.search.Article import de.mm20.launcher2.search.CalendarEvent import de.mm20.launcher2.search.Contact import de.mm20.launcher2.search.File +import de.mm20.launcher2.search.Location import de.mm20.launcher2.search.SavableSearchable +import de.mm20.launcher2.search.SearchFilters import de.mm20.launcher2.search.SearchService import de.mm20.launcher2.search.Searchable -import de.mm20.launcher2.search.Location -import de.mm20.launcher2.search.SearchFilters import de.mm20.launcher2.search.Website import de.mm20.launcher2.search.data.Calculator import de.mm20.launcher2.search.data.UnitConverter @@ -36,17 +35,14 @@ import de.mm20.launcher2.searchable.VisibilityLevel import de.mm20.launcher2.searchactions.actions.SearchAction import de.mm20.launcher2.services.favorites.FavoritesService import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first -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 @@ -78,6 +74,7 @@ class SearchVM : ViewModel(), KoinComponent { val locationResults = mutableStateOf>(emptyList()) val appResults = mutableStateOf>(emptyList()) val workAppResults = mutableStateOf>(emptyList()) + val privateSpaceAppResults = mutableStateOf>(emptyList()) val appShortcutResults = mutableStateOf>(emptyList()) val fileResults = mutableStateOf>(emptyList()) val contactResults = mutableStateOf>(emptyList()) @@ -96,7 +93,11 @@ class SearchVM : ViewModel(), KoinComponent { val showFilters = mutableStateOf(false) - private val defaultFilters = searchFilterSettings.defaultFilter.stateIn(viewModelScope, SharingStarted.Eagerly, SearchFilters()) + private val defaultFilters = searchFilterSettings.defaultFilter.stateIn( + viewModelScope, + SharingStarted.Eagerly, + SearchFilters() + ) val filters = mutableStateOf(defaultFilters.value) val filterBar = searchFilterSettings.filterBar val filterBarItems = searchFilterSettings.filterBarItems @@ -174,146 +175,191 @@ class SearchVM : ViewModel(), KoinComponent { } catch (_: CancellationException) { } hideFavorites.value = query.isNotEmpty() + searchJob = viewModelScope.launch { - searchUiSettings.resultOrder.collectLatest { resultOrder -> - searchService.search( - query, - filters = if (query.isEmpty()) filters.copy(apps = true) else filters, - ).collectLatest { results -> - var resultsList = withContext(Dispatchers.Default) { - listOfNotNull( - results.apps, - results.other, - results.shortcuts, - results.files, - results.contacts, - results.calendars, - results.locations, - results.wikipedia, - results.websites, - results.calculators, - results.unitConverters, - results.searchActions, - ).flatten() - .distinctBy { if (it is SavableSearchable) it.key else it } - .sortedBy { (it as? SavableSearchable) } - } + if (query.isEmpty()) { + val hiddenItemKeys = searchableRepository.getKeys( + maxVisibility = VisibilityLevel.SearchOnly, + includeTypes = listOf("app"), + ) + val allApps = searchService.getAllApps() + fileResults.value = emptyList() + contactResults.value = emptyList() + calendarResults.value = emptyList() + locationResults.value = emptyList() + articleResults.value = emptyList() + websiteResults.value = emptyList() + calculatorResults.value = emptyList() + unitConverterResults.value = emptyList() + searchActionResults.value = emptyList() - val relevance = - if (query.isEmpty()) { - emptyList() - } else { - val keys = resultsList.mapNotNull { (it as? SavableSearchable)?.key } - when (resultOrder) { + allApps + .combine(hiddenItemKeys) { results, hiddenKeys -> results to hiddenKeys } + .collectLatest { (results, hiddenKeys) -> + val hiddenItems = mutableListOf() - SearchResultOrder.LaunchCount -> searchableRepository.sortByRelevance( - keys - ).first() - - SearchResultOrder.Weighted -> searchableRepository.sortByWeight( - keys - ).first() - - else -> emptyList() - } + val (hiddenApps, apps) = results.standardProfileApps.partition { + hiddenKeys.contains( + it.key + ) } + hiddenItems += hiddenApps - resultsList = resultsList.sortedWith { a, b -> - val lastLocation = devicePoseProvider.lastLocation - when { - a is Location && b is Location && lastLocation != null -> { - a.distanceTo(lastLocation) - .compareTo(b.distanceTo(lastLocation)) - } - - a is SavableSearchable && b !is SavableSearchable -> -1 - a !is SavableSearchable && b is SavableSearchable -> 1 - a is SavableSearchable && b is SavableSearchable -> { - val aKey = a.key - val bKey = b.key - val aRank = relevance.indexOf(aKey) - val bRank = relevance.indexOf(bKey) - when { - aRank != -1 && bRank != -1 -> aRank.compareTo(bRank) - aRank == -1 && bRank != -1 -> 1 - aRank != -1 && bRank == -1 -> -1 - else -> a.compareTo(b) - } - } - - else -> 0 + val (hiddenWorkApps, workApps) = results.workProfileApps.partition { + hiddenKeys.contains( + it.key + ) } - } + hiddenItems += hiddenWorkApps - val hiddenItemKeys = searchableRepository.getKeys( - maxVisibility = if (query.isEmpty()) VisibilityLevel.SearchOnly else VisibilityLevel.Hidden, - ) - - hiddenItemKeys.collectLatest { hiddenKeys -> - val hidden = mutableListOf() - val apps = mutableListOf() - val workApps = mutableListOf() - val shortcuts = mutableListOf() - val files = mutableListOf() - val contacts = mutableListOf() - val events = mutableListOf() - val unitConv = mutableListOf() - val calc = mutableListOf() - val articles = mutableListOf
() - val locations = mutableListOf() - val website = mutableListOf() - val actions = mutableListOf() - for (r in resultsList) { - when { - r is SavableSearchable && hiddenKeys.contains(r.key) && !filters.hiddenItems -> { - hidden.add(r) - } - r is Application && r.user != Process.myUserHandle() -> workApps.add(r) - r is Application -> apps.add(r) - r is AppShortcut -> shortcuts.add(r) - r is File -> files.add(r) - r is Contact -> contacts.add(r) - r is CalendarEvent -> events.add(r) - r is UnitConverter -> unitConv.add(r) - r is Calculator -> calc.add(r) - r is Website -> website.add(r) - r is Article -> articles.add(r) - r is Location -> locations.add(r) - r is SearchAction -> actions.add(r) - } - } - - if (query.isNotEmpty() && launchOnEnter.value) { - bestMatch.value = listOf( - apps, - workApps, - shortcuts, - unitConv, - calc, - events, - locations, - contacts, - articles, - website, - files, - actions - ).firstNotNullOfOrNull { it.firstOrNull() } + val (hiddenPrivateApps, privateApps) = results.privateSpaceApps.partition { + hiddenKeys.contains( + it.key + ) } + hiddenItems += hiddenPrivateApps appResults.value = apps workAppResults.value = workApps - appShortcutResults.value = shortcuts - fileResults.value = files - contactResults.value = contacts - calendarResults.value = events - articleResults.value = articles - locationResults.value = locations - websiteResults.value = website - calculatorResults.value = calc - unitConverterResults.value = unitConv - hiddenResults.value = hidden - if (results.searchActions != null) searchActionResults.value = actions + privateSpaceAppResults.value = privateApps + hiddenResults.value = hiddenItems } + + } else { + val hiddenItemKeys = searchableRepository.getKeys( + maxVisibility = VisibilityLevel.Hidden, + ) + searchUiSettings.resultOrder.collectLatest { resultOrder -> + searchService.search( + query, + filters = if (query.isEmpty()) filters.copy(apps = true) else filters, + ) + .combine(hiddenItemKeys) { results, hiddenKeys -> results to hiddenKeys } + .collectLatest { (results, hiddenKeys) -> + val hiddenItems = mutableListOf() + + if (results.apps != null) { + val (hiddenApps, apps) = results.apps!!.partition { + hiddenKeys.contains( + it.key + ) + } + hiddenItems += hiddenApps + appResults.value = apps.applyRanking(resultOrder) + } else { + appResults.value = emptyList() + } + workAppResults.value = emptyList() + privateSpaceAppResults.value = emptyList() + + if (results.shortcuts != null) { + val (hiddenShortcuts, shortcuts) = results.shortcuts!!.partition { + hiddenKeys.contains( + it.key + ) + } + hiddenItems += hiddenShortcuts + appShortcutResults.value = shortcuts.applyRanking(resultOrder) + } else { + appShortcutResults.value = emptyList() + } + + if (results.files != null) { + val (hiddenFiles, files) = results.files!!.partition { + hiddenKeys.contains( + it.key + ) + } + hiddenItems += hiddenFiles + fileResults.value = files.applyRanking(resultOrder) + } else { + fileResults.value = emptyList() + } + + if (results.contacts != null) { + val (hiddenContacts, contacts) = results.contacts!!.partition { + hiddenKeys.contains( + it.key + ) + } + hiddenItems += hiddenContacts + contactResults.value = contacts.applyRanking(resultOrder) + } else { + contactResults.value = emptyList() + } + + if (results.calendars != null) { + val (hiddenEvents, events) = results.calendars!!.partition { + hiddenKeys.contains( + it.key + ) + } + hiddenItems += hiddenEvents + calendarResults.value = events.applyRanking(resultOrder) + } else { + calendarResults.value = emptyList() + } + + if (results.locations != null && results.locations!!.isNotEmpty()) { + val (hiddenLocations, locations) = results.locations!!.partition { + hiddenKeys.contains( + it.key + ) + } + hiddenItems += hiddenLocations + val lastLocation = devicePoseProvider.lastLocation + if (lastLocation != null) { + locationResults.value = locations.asSequence() + .sortedWith { a, b -> + a.distanceTo(lastLocation) + .compareTo(b.distanceTo(lastLocation)) + } + .distinct() + .toList() + } else { + locationResults.value = locations.applyRanking(resultOrder) + } + } else { + locationResults.value = emptyList() + } + + if (results.wikipedia != null) { + articleResults.value = results.wikipedia!!.applyRanking(resultOrder) + } else { + articleResults.value = emptyList() + } + + if (results.websites != null) { + websiteResults.value = results.websites!!.applyRanking(resultOrder) + } else { + websiteResults.value = emptyList() + } + + + calculatorResults.value = results.calculators ?: emptyList() + unitConverterResults.value = results.unitConverters ?: emptyList() + + if (results.searchActions != null) { + searchActionResults.value = results.searchActions!! + } + + if (launchOnEnter.value) { + bestMatch.value = when { + appResults.value.isNotEmpty() -> appResults.value.first() + appShortcutResults.value.isNotEmpty() -> appShortcutResults.value.first() + calendarResults.value.isNotEmpty() -> calendarResults.value.first() + locationResults.value.isNotEmpty() -> locationResults.value.first() + contactResults.value.isNotEmpty() -> contactResults.value.first() + articleResults.value.isNotEmpty() -> articleResults.value.first() + websiteResults.value.isNotEmpty() -> websiteResults.value.first() + fileResults.value.isNotEmpty() -> fileResults.value.first() + searchActionResults.value.isNotEmpty() -> searchActionResults.value.first() + else -> null + } + } else { + bestMatch.value = null + } + } } } } @@ -387,8 +433,30 @@ class SearchVM : ViewModel(), KoinComponent { fun expandCategory(category: SearchCategory) { expandedCategory.value = category } + + private suspend fun List.applyRanking(order: SearchResultOrder): List { + if (size <= 1) return this + val sequence = asSequence() + val sorted = if (order == SearchResultOrder.Weighted) { + val sortedKeys = searchableRepository.sortByWeight(map { it.key }).first() + sequence.sortedWith { a, b -> + val aRank = sortedKeys.indexOf(a.key) + val bRank = sortedKeys.indexOf(b.key) + when { + aRank != -1 && bRank != -1 -> aRank.compareTo(bRank) + aRank == -1 && bRank != -1 -> 1 + aRank != -1 && bRank == -1 -> -1 + else -> a.compareTo(b) + } + } + } else { + sequence.sorted() + } + return sorted.distinct().toList() + } } + enum class SearchCategory { Apps, Calculator, diff --git a/core/base/src/main/java/de/mm20/launcher2/search/SearchableRepository.kt b/core/base/src/main/java/de/mm20/launcher2/search/SearchableRepository.kt index be6761a9..65ffabbf 100644 --- a/core/base/src/main/java/de/mm20/launcher2/search/SearchableRepository.kt +++ b/core/base/src/main/java/de/mm20/launcher2/search/SearchableRepository.kt @@ -4,5 +4,5 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.flow.Flow interface SearchableRepository { - fun search(query: String, allowNetwork: Boolean): Flow> + fun search(query: String, allowNetwork: Boolean): Flow> } \ No newline at end of file diff --git a/core/profiles/src/main/java/de/mm20/launcher2/profiles/ProfileManager.kt b/core/profiles/src/main/java/de/mm20/launcher2/profiles/ProfileManager.kt index 3bcd47d1..d941ffcd 100644 --- a/core/profiles/src/main/java/de/mm20/launcher2/profiles/ProfileManager.kt +++ b/core/profiles/src/main/java/de/mm20/launcher2/profiles/ProfileManager.kt @@ -14,6 +14,7 @@ import androidx.core.content.getSystemService import de.mm20.launcher2.ktx.isAtLeastApiLevel import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionsManager +import de.mm20.launcher2.plugin.data.get import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -107,6 +108,7 @@ class ProfileManager( ) ) } + Log.d("MM20", "Profiles: $profiles") profileStates.value = profiles } } @@ -127,8 +129,9 @@ class ProfileManager( * This only works when the launcher is installed in the primary profile. */ private fun getProfileType(userHandle: UserHandle): Profile.Type { + val restrictions = userManager.getUserRestrictions(userHandle) return when { - userManager.isManagedProfile(userHandle) -> Profile.Type.Work + restrictions.getBoolean(UserManager.ALLOW_PARENT_PROFILE_APP_LINKING) -> Profile.Type.Work userHandle == Process.myUserHandle() -> Profile.Type.Personal else -> Profile.Type.Private } @@ -137,7 +140,7 @@ class ProfileManager( private fun getProfileState(userHandle: UserHandle): Profile.State { return Profile.State( locked = !userManager.isUserUnlocked(userHandle), - hidden = !userManager.isUserUnlocked(userHandle), + hidden = !userManager.isUserRunning(userHandle), ) } @@ -151,18 +154,4 @@ class ProfileManager( userManager.requestQuietModeEnabled(true, profile.userHandle) } -} - -internal fun UserManager.isManagedProfile(userHandle: UserHandle): Boolean { - try { - val isManagedProfile = UserManager::class.java.getDeclaredMethod( - "isManagedProfile", - Int::class.javaPrimitiveType - ) - val serial = getSerialNumberForUser(userHandle).toInt() - return isManagedProfile.invoke(this, serial) as Boolean - } catch (e: Exception) { - Log.e("MM20", "isManagedProfile could not be invoked", e) - return false - } } \ No newline at end of file diff --git a/services/search/build.gradle.kts b/services/search/build.gradle.kts index fbb97dc5..e24d9772 100644 --- a/services/search/build.gradle.kts +++ b/services/search/build.gradle.kts @@ -53,6 +53,7 @@ dependencies { implementation(project(":core:base")) implementation(project(":core:preferences")) + implementation(project(":core:profiles")) implementation(project(":core:crashreporter")) implementation(project(":core:ktx")) } \ No newline at end of file diff --git a/services/search/src/main/java/de/mm20/launcher2/search/Module.kt b/services/search/src/main/java/de/mm20/launcher2/search/Module.kt index a560c889..391f0c1d 100644 --- a/services/search/src/main/java/de/mm20/launcher2/search/Module.kt +++ b/services/search/src/main/java/de/mm20/launcher2/search/Module.kt @@ -18,6 +18,7 @@ val searchModule = module { get(named()), get(), get(), + get(), ) } } \ No newline at end of file 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 99ecb29d..4e17980a 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 @@ -3,19 +3,24 @@ package de.mm20.launcher2.search import de.mm20.launcher2.calculator.CalculatorRepository import de.mm20.launcher2.data.customattrs.CustomAttributesRepository import de.mm20.launcher2.data.customattrs.utils.withCustomLabels +import de.mm20.launcher2.profiles.Profile +import de.mm20.launcher2.profiles.ProfileManager import de.mm20.launcher2.search.data.Calculator import de.mm20.launcher2.search.data.UnitConverter import de.mm20.launcher2.searchactions.SearchActionService import de.mm20.launcher2.searchactions.actions.SearchAction import de.mm20.launcher2.unitconverter.UnitConverterRepository -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope @@ -25,6 +30,8 @@ interface SearchService { query: String, filters: SearchFilters, ): Flow + + fun getAllApps(): Flow } internal class SearchServiceImpl( @@ -40,14 +47,56 @@ internal class SearchServiceImpl( private val websiteRepository: SearchableRepository, private val searchActionService: SearchActionService, private val customAttributesRepository: CustomAttributesRepository, + private val profileManager: ProfileManager, ) : SearchService { override fun search( query: String, filters: SearchFilters, - ): Flow = channelFlow { - val results = MutableStateFlow(SearchResults()) + ): Flow = flow { supervisorScope { + val results = MutableStateFlow(SearchResults()) + + val customAttrResults = customAttributesRepository.search(query) + .map { items -> + val apps = mutableListOf() + val shortcuts = mutableListOf() + val contacts = mutableListOf() + val events = mutableListOf() + val files = mutableListOf() + val unitConverters = mutableListOf() + val websites = mutableListOf() + val wikipedia = mutableListOf
() + val locations = mutableListOf() + val searchActions = mutableListOf() + for (it in items) { + when (it) { + is Application -> if (filters.apps) apps.add(it) + is AppShortcut -> if (filters.shortcuts) shortcuts.add(it) + is Contact -> if (filters.contacts) contacts.add(it) + is CalendarEvent -> if (filters.events) events.add(it) + is File -> if (filters.files) files.add(it) + is UnitConverter -> if (filters.tools) unitConverters.add(it) + is Website -> if (filters.websites) websites.add(it) + is Article -> if (filters.articles) wikipedia.add(it) + is Location -> if (filters.places) locations.add(it) + is SearchAction -> searchActions.add(it) + } + } + SearchResults( + apps = apps, + shortcuts = shortcuts, + contacts = contacts, + calendars = events, + files = files, + unitConverters = unitConverters, + websites = websites, + wikipedia = wikipedia, + locations = locations, + searchActions = searchActions, + ) + }.shareIn(this, SharingStarted.WhileSubscribed(), 1) + launch { searchActionService.search(query) .collectLatest { r -> @@ -59,10 +108,14 @@ internal class SearchServiceImpl( if (filters.apps) { launch { appRepository.search(query, filters.allowNetwork) + .combine(customAttrResults) { apps, customAttrs -> + if (customAttrs.apps != null) apps + customAttrs.apps + else apps + } .withCustomLabels(customAttributesRepository) .collectLatest { r -> results.update { - it.copy(apps = r.toImmutableList()) + it.copy(apps = r) } } } @@ -70,10 +123,14 @@ internal class SearchServiceImpl( if (filters.shortcuts) { launch { appShortcutRepository.search(query, filters.allowNetwork) + .combine(customAttrResults) { shortcuts, customAttrs -> + if (customAttrs.shortcuts != null) shortcuts + customAttrs.shortcuts + else shortcuts + } .withCustomLabels(customAttributesRepository) .collectLatest { r -> results.update { - it.copy(shortcuts = r.toImmutableList()) + it.copy(shortcuts = r) } } } @@ -81,10 +138,14 @@ internal class SearchServiceImpl( if (filters.contacts) { launch { contactRepository.search(query, filters.allowNetwork) + .combine(customAttrResults) { contacts, customAttrs -> + if (customAttrs.contacts != null) contacts + customAttrs.contacts + else contacts + } .withCustomLabels(customAttributesRepository) .collectLatest { r -> results.update { - it.copy(contacts = r.toImmutableList()) + it.copy(contacts = r) } } } @@ -92,10 +153,14 @@ internal class SearchServiceImpl( if (filters.events) { launch { calendarRepository.search(query, filters.allowNetwork) + .combine(customAttrResults) { calendars, customAttrs -> + if (customAttrs.calendars != null) calendars + customAttrs.calendars + else calendars + } .withCustomLabels(customAttributesRepository) .collectLatest { r -> results.update { - it.copy(calendars = r.toImmutableList()) + it.copy(calendars = r) } } } @@ -104,8 +169,8 @@ internal class SearchServiceImpl( launch { calculatorRepository.search(query).collectLatest { r -> results.update { - it.copy(calculators = r?.let { persistentListOf(it) } - ?: persistentListOf()) + it.copy(calculators = r?.let { listOf(it) } + ?: listOf()) } } } @@ -113,8 +178,8 @@ internal class SearchServiceImpl( unitConverterRepository.search(query) .collectLatest { r -> results.update { - it.copy(unitConverters = r?.let { persistentListOf(it) } - ?: persistentListOf()) + it.copy(unitConverters = r?.let { listOf(it) } + ?: listOf()) } } } @@ -122,10 +187,14 @@ internal class SearchServiceImpl( if (filters.websites) { launch { websiteRepository.search(query, filters.allowNetwork) + .combine(customAttrResults) { websites, customAttrs -> + if (customAttrs.websites != null) websites + customAttrs.websites + else websites + } .withCustomLabels(customAttributesRepository) .collectLatest { r -> results.update { - it.copy(websites = r.toImmutableList()) + it.copy(websites = r) } } } @@ -134,10 +203,14 @@ internal class SearchServiceImpl( launch { delay(750) articleRepository.search(query, filters.allowNetwork) + .combine(customAttrResults) { articles, customAttrs -> + if (customAttrs.wikipedia != null) articles + customAttrs.wikipedia + else articles + } .withCustomLabels(customAttributesRepository) .collectLatest { r -> results.update { - it.copy(wikipedia = r.toImmutableList()) + it.copy(wikipedia = r) } } } @@ -145,10 +218,14 @@ internal class SearchServiceImpl( if (filters.places) { launch { locationRepository.search(query, filters.allowNetwork) + .combine(customAttrResults) { locations, customAttrs -> + if (customAttrs.locations != null) locations + customAttrs.locations + else locations + } .withCustomLabels(customAttributesRepository) .collectLatest { r -> results.update { - it.copy(locations = r.toImmutableList()) + it.copy(locations = r) } } } @@ -159,56 +236,65 @@ internal class SearchServiceImpl( query, filters.allowNetwork ) + .combine(customAttrResults) { files, customAttrs -> + if (customAttrs.files != null) files + customAttrs.files + else files + } .withCustomLabels(customAttributesRepository) .collectLatest { r -> results.update { - it.copy(files = r.toImmutableList()) + it.copy(files = r) } } } } - launch { - customAttributesRepository.search(query) - .withCustomLabels(customAttributesRepository) - .collectLatest { r -> - results.update { - it.copy(other = r - .filter { - filters.apps && it is Application || - filters.shortcuts && it is AppShortcut || - filters.contacts && it is Contact || - filters.events && it is CalendarEvent || - filters.files && it is File || - filters.websites && it is Website || - filters.articles && it is Article || - filters.places && it is Location - } - .toImmutableList() - ) - } - } - } - launch { - results.collectLatest { send(it) } - } + emitAll(results) + } + } + override fun getAllApps(): Flow { + return profileManager.activeProfiles.flatMapLatest { profiles -> + val standardProfile = profiles.find { it.type == Profile.Type.Personal } + val workProfile = profiles.find { it.type == Profile.Type.Work } + val privateSpace = profiles.find { it.type == Profile.Type.Private } + appRepository.search("", false) + .withCustomLabels(customAttributesRepository) + .map { + val grouped = it.groupBy { it.user } + val standardProfileApps = + standardProfile?.let { grouped[it.userHandle] } ?: emptyList() + val workProfileApps = workProfile?.let { grouped[it.userHandle] } ?: emptyList() + val privateSpaceApps = + privateSpace?.let { grouped[it.userHandle] } ?: emptyList() + + AllAppsResults( + standardProfileApps = standardProfileApps.sorted(), + workProfileApps = workProfileApps.sorted(), + privateSpaceApps = privateSpaceApps.sorted(), + ) + } } } } 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 locations: ImmutableList? = null, - val searchActions: ImmutableList? = null, - val other: ImmutableList? = null, + val apps: List? = null, + val shortcuts: List? = null, + val contacts: List? = null, + val calendars: List? = null, + val files: List? = null, + val calculators: List? = null, + val unitConverters: List? = null, + val websites: List? = null, + val wikipedia: List
? = null, + val locations: List? = null, + val searchActions: List? = null, +) + +data class AllAppsResults( + val standardProfileApps: List, + val workProfileApps: List, + val privateSpaceApps: List, ) fun SearchResults.toList(): List { @@ -223,6 +309,5 @@ fun SearchResults.toList(): List { websites, wikipedia, searchActions, - other, ).flatten() } \ No newline at end of file