Refactor search

This commit is contained in:
MM20 2024-07-17 23:27:00 +02:00
parent 5a28eb584e
commit c7804296d7
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
6 changed files with 351 additions and 207 deletions

View File

@ -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<List<Location>>(emptyList())
val appResults = mutableStateOf<List<Application>>(emptyList())
val workAppResults = mutableStateOf<List<Application>>(emptyList())
val privateSpaceAppResults = mutableStateOf<List<Application>>(emptyList())
val appShortcutResults = mutableStateOf<List<AppShortcut>>(emptyList())
val fileResults = mutableStateOf<List<File>>(emptyList())
val contactResults = mutableStateOf<List<Contact>>(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<SavableSearchable>()
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<SavableSearchable>()
val apps = mutableListOf<Application>()
val workApps = mutableListOf<Application>()
val shortcuts = mutableListOf<AppShortcut>()
val files = mutableListOf<File>()
val contacts = mutableListOf<Contact>()
val events = mutableListOf<CalendarEvent>()
val unitConv = mutableListOf<UnitConverter>()
val calc = mutableListOf<Calculator>()
val articles = mutableListOf<Article>()
val locations = mutableListOf<Location>()
val website = mutableListOf<Website>()
val actions = mutableListOf<SearchAction>()
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<SavableSearchable>()
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 <T : SavableSearchable> List<T>.applyRanking(order: SearchResultOrder): List<T> {
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,

View File

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

View File

@ -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),
)
}
@ -152,17 +155,3 @@ class ProfileManager(
}
}
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
}
}

View File

@ -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"))
}

View File

@ -18,6 +18,7 @@ val searchModule = module {
get(named<Website>()),
get(),
get(),
get(),
)
}
}

View File

@ -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<SearchResults>
fun getAllApps(): Flow<AllAppsResults>
}
internal class SearchServiceImpl(
@ -40,14 +47,56 @@ internal class SearchServiceImpl(
private val websiteRepository: SearchableRepository<Website>,
private val searchActionService: SearchActionService,
private val customAttributesRepository: CustomAttributesRepository,
private val profileManager: ProfileManager,
) : SearchService {
override fun search(
query: String,
filters: SearchFilters,
): Flow<SearchResults> = channelFlow {
val results = MutableStateFlow(SearchResults())
): Flow<SearchResults> = flow {
supervisorScope {
val results = MutableStateFlow(SearchResults())
val customAttrResults = customAttributesRepository.search(query)
.map { items ->
val apps = mutableListOf<Application>()
val shortcuts = mutableListOf<AppShortcut>()
val contacts = mutableListOf<Contact>()
val events = mutableListOf<CalendarEvent>()
val files = mutableListOf<File>()
val unitConverters = mutableListOf<UnitConverter>()
val websites = mutableListOf<Website>()
val wikipedia = mutableListOf<Article>()
val locations = mutableListOf<Location>()
val searchActions = mutableListOf<SearchAction>()
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<AllAppsResults> {
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<Application>? = null,
val shortcuts: ImmutableList<AppShortcut>? = null,
val contacts: ImmutableList<Contact>? = null,
val calendars: ImmutableList<CalendarEvent>? = null,
val files: ImmutableList<File>? = null,
val calculators: ImmutableList<Calculator>? = null,
val unitConverters: ImmutableList<UnitConverter>? = null,
val websites: ImmutableList<Website>? = null,
val wikipedia: ImmutableList<Article>? = null,
val locations: ImmutableList<Location>? = null,
val searchActions: ImmutableList<SearchAction>? = null,
val other: ImmutableList<SavableSearchable>? = null,
val apps: List<Application>? = null,
val shortcuts: List<AppShortcut>? = null,
val contacts: List<Contact>? = null,
val calendars: List<CalendarEvent>? = null,
val files: List<File>? = null,
val calculators: List<Calculator>? = null,
val unitConverters: List<UnitConverter>? = null,
val websites: List<Website>? = null,
val wikipedia: List<Article>? = null,
val locations: List<Location>? = null,
val searchActions: List<SearchAction>? = null,
)
data class AllAppsResults(
val standardProfileApps: List<Application>,
val workProfileApps: List<Application>,
val privateSpaceApps: List<Application>,
)
fun SearchResults.toList(): List<Searchable> {
@ -223,6 +309,5 @@ fun SearchResults.toList(): List<Searchable> {
websites,
wikipedia,
searchActions,
other,
).flatten()
}