Add app shortcut search

This commit is contained in:
MM20 2022-03-19 17:41:04 +01:00
parent b73c9fabc9
commit ab114595c4
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
4 changed files with 116 additions and 12 deletions

View File

@ -3,26 +3,35 @@ package de.mm20.launcher2.appshortcuts
import android.content.Context
import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps
import android.content.pm.ShortcutInfo
import android.os.UserHandle
import android.content.pm.PackageManager
import android.os.Process
import androidx.core.content.getSystemService
import com.github.promeg.pinyinhelper.Pinyin
import de.mm20.launcher2.search.data.AppShortcut
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.withContext
import org.apache.commons.text.similarity.FuzzyScore
import java.util.*
interface AppShortcutRepository {
suspend fun getShortcutsForActivity(launcherActivityInfo: LauncherActivityInfo, count: Int = 5): List<AppShortcut>
suspend fun getShortcutsForActivity(
launcherActivityInfo: LauncherActivityInfo,
count: Int = 5
): List<AppShortcut>
fun search(query: String): Flow<List<AppShortcut>>
}
internal class AppShortcutRepositoryImpl(
private val context: Context
): AppShortcutRepository {
) : AppShortcutRepository {
override suspend fun getShortcutsForActivity(
launcherActivityInfo: LauncherActivityInfo,
count: Int,
) = withContext(Dispatchers.IO){
) = withContext(Dispatchers.IO) {
val launcherApps = context.getSystemService<LauncherApps>()!!
if (!launcherApps.hasShortcutHostPermission()) return@withContext emptyList()
val query = LauncherApps.ShortcutQuery()
@ -40,12 +49,68 @@ internal class AppShortcutRepositoryImpl(
else it
}
?.map {
AppShortcut(
context,
it,
launcherActivityInfo.label.toString()
)
} ?: emptyList())
AppShortcut(
context,
it,
launcherActivityInfo.label.toString()
)
} ?: emptyList())
appShortcuts
}
override fun search(query: String) = channelFlow<List<AppShortcut>> {
val launcherApps = context.getSystemService<LauncherApps>() ?: return@channelFlow send(
emptyList()
)
if (query.length < 3) {
return@channelFlow send(emptyList())
}
withContext(Dispatchers.IO) {
val shortcutQuery = LauncherApps.ShortcutQuery()
shortcutQuery.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED or
LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC or
LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST or
LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED or
LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER)
val shortcuts = launcherApps.getShortcuts(shortcutQuery, Process.myUserHandle())
?.filter {
if (it.longLabel != null) {
return@filter matches(it.longLabel.toString(), query)
}
if (it.shortLabel != null) {
return@filter matches(it.shortLabel.toString(), query)
}
return@filter false
} ?: emptyList()
val pm = context.packageManager
send(shortcuts.map {
val label = try {
pm.getApplicationInfo(it.`package`, 0).loadLabel(pm).toString()
} catch (e: PackageManager.NameNotFoundException) {
""
}
AppShortcut(
context,
it,
label
)
}.sorted())
}
}
private fun matches(label: String, query: String): Boolean {
val labelLatin = romanize(label)
val fuzzyScore = FuzzyScore(Locale.getDefault())
return fuzzyScore.fuzzyScore(label, query) >= query.length * 1.5 ||
fuzzyScore.fuzzyScore(labelLatin, query) >= query.length * 1.5
}
private fun romanize(label: String): String {
return Pinyin.toPinyin(label, "").lowercase(Locale.getDefault())
}
}

View File

@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.applications.AppRepository
import de.mm20.launcher2.appshortcuts.AppShortcutRepository
import de.mm20.launcher2.calculator.CalculatorRepository
import de.mm20.launcher2.calendar.CalendarRepository
import de.mm20.launcher2.contacts.ContactRepository
@ -36,6 +37,7 @@ class SearchVM : ViewModel(), KoinComponent {
private val calendarRepository: CalendarRepository by inject()
private val contactRepository: ContactRepository by inject()
private val appRepository: AppRepository by inject()
private val appShortcutRepository: AppShortcutRepository by inject()
private val wikipediaRepository: WikipediaRepository by inject()
private val unitConverterRepository: UnitConverterRepository by inject()
private val calculatorRepository: CalculatorRepository by inject()
@ -52,6 +54,7 @@ class SearchVM : ViewModel(), KoinComponent {
}
val appResults = MutableLiveData<List<Application>>(emptyList())
val appShortcutResults = MutableLiveData<List<AppShortcut>>(emptyList())
val fileResults = MutableLiveData<List<File>>(emptyList())
val contactResults = MutableLiveData<List<Contact>>(emptyList())
val calendarResults = MutableLiveData<List<CalendarEvent>>(emptyList())
@ -124,6 +127,11 @@ class SearchVM : ViewModel(), KoinComponent {
websearchResults.postValue(it)
}
}
jobs += async {
appShortcutRepository.search(query).collectLatest {
appShortcutResults.postValue(it)
}
}
jobs.map { it.await() }
isSearching.postValue(false)
}

View File

@ -13,6 +13,7 @@ import androidx.compose.ui.unit.dp
import de.mm20.launcher2.ui.MdcLauncherTheme
import de.mm20.launcher2.ui.base.ProvideSettings
import de.mm20.launcher2.ui.launcher.search.apps.AppResults
import de.mm20.launcher2.ui.launcher.search.appshortcuts.AppShortcutResults
import de.mm20.launcher2.ui.launcher.search.calculator.CalculatorResults
import de.mm20.launcher2.ui.launcher.search.calendar.CalendarResults
import de.mm20.launcher2.ui.launcher.search.contacts.ContactResults
@ -44,6 +45,7 @@ class SearchView @JvmOverloads constructor(
) {
FavoritesResults()
AppResults()
AppShortcutResults()
UnitConverterResults()
CalculatorResults()
CalendarResults()

View File

@ -0,0 +1,29 @@
package de.mm20.launcher2.ui.launcher.search.appshortcuts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.SearchResultGrid
@Composable
fun ColumnScope.AppShortcutResults() {
val viewModel: SearchVM = viewModel()
val apps by viewModel.appShortcutResults.observeAsState(emptyList())
AnimatedVisibility(apps.isNotEmpty()) {
LauncherCard(
modifier = Modifier.padding(bottom = 8.dp)
) {
SearchResultGrid(items = apps)
}
}
}