Refactor search
This commit is contained in:
parent
5a28eb584e
commit
c7804296d7
@ -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,
|
||||
|
||||
@ -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>>
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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"))
|
||||
}
|
||||
@ -18,6 +18,7 @@ val searchModule = module {
|
||||
get(named<Website>()),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user