Add preferences for app shortcut search
This commit is contained in:
parent
a4a562e335
commit
d6ebbd193a
@ -44,6 +44,7 @@ dependencies {
|
|||||||
implementation(libs.tinypinyin)
|
implementation(libs.tinypinyin)
|
||||||
|
|
||||||
implementation(project(":search"))
|
implementation(project(":search"))
|
||||||
|
implementation(project(":permissions"))
|
||||||
implementation(project(":base"))
|
implementation(project(":base"))
|
||||||
implementation(project(":preferences"))
|
implementation(project(":preferences"))
|
||||||
implementation(project(":ktx"))
|
implementation(project(":ktx"))
|
||||||
|
|||||||
@ -7,10 +7,14 @@ import android.content.pm.PackageManager
|
|||||||
import android.os.Process
|
import android.os.Process
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import com.github.promeg.pinyinhelper.Pinyin
|
import com.github.promeg.pinyinhelper.Pinyin
|
||||||
|
import de.mm20.launcher2.permissions.PermissionsManager
|
||||||
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
import de.mm20.launcher2.search.data.AppShortcut
|
import de.mm20.launcher2.search.data.AppShortcut
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.channelFlow
|
import kotlinx.coroutines.flow.channelFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
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.*
|
||||||
@ -25,7 +29,9 @@ interface AppShortcutRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal class AppShortcutRepositoryImpl(
|
internal class AppShortcutRepositoryImpl(
|
||||||
private val context: Context
|
private val context: Context,
|
||||||
|
private val permissionsManager: PermissionsManager,
|
||||||
|
private val dataStore: LauncherDataStore,
|
||||||
) : AppShortcutRepository {
|
) : AppShortcutRepository {
|
||||||
|
|
||||||
override suspend fun getShortcutsForActivity(
|
override suspend fun getShortcutsForActivity(
|
||||||
@ -59,46 +65,56 @@ internal class AppShortcutRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun search(query: String) = channelFlow<List<AppShortcut>> {
|
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) {
|
withContext(Dispatchers.IO) {
|
||||||
val shortcutQuery = LauncherApps.ShortcutQuery()
|
dataStore.data.map { it.appShortcutSearch.enabled }.collectLatest { enabled ->
|
||||||
shortcutQuery.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED or
|
if (!enabled) {
|
||||||
LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC or
|
send(emptyList())
|
||||||
LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST or
|
return@collectLatest
|
||||||
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,
|
if (query.length < 3) {
|
||||||
it,
|
return@collectLatest send(emptyList())
|
||||||
label
|
}
|
||||||
|
|
||||||
|
val launcherApps =
|
||||||
|
context.getSystemService<LauncherApps>() ?: return@collectLatest send(
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
)
|
)
|
||||||
}.sorted())
|
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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,5 +4,5 @@ import org.koin.android.ext.koin.androidContext
|
|||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val appShortcutsModule = module {
|
val appShortcutsModule = module {
|
||||||
single<AppShortcutRepository> { AppShortcutRepositoryImpl(androidContext()) }
|
single<AppShortcutRepository> { AppShortcutRepositoryImpl(androidContext(), get(), get()) }
|
||||||
}
|
}
|
||||||
@ -356,6 +356,8 @@
|
|||||||
<string name="missing_permission_calendar_search_settings">Calendar permission is required to search calendar</string>
|
<string name="missing_permission_calendar_search_settings">Calendar permission is required to search calendar</string>
|
||||||
<!-- Missing calendar permission in calendar widget settings screen -->
|
<!-- Missing calendar permission in calendar widget settings screen -->
|
||||||
<string name="missing_permission_calendar_widget_settings">This widget requires calendar permission</string>
|
<string name="missing_permission_calendar_widget_settings">This widget requires calendar permission</string>
|
||||||
|
<!-- Missing permission app shortcuts permission in search settings screen. %1$s: app name -->
|
||||||
|
<string name="missing_permission_appshortcuts_search_settings">%1$s must be set as default home app to search app shortcuts</string>
|
||||||
<!-- Missing file permission in search settings screen -->
|
<!-- Missing file permission in search settings screen -->
|
||||||
<string name="missing_permission_file_search_settings">External storage permission is required to search local files</string>
|
<string name="missing_permission_file_search_settings">External storage permission is required to search local files</string>
|
||||||
<!-- Missing file permission in search settings screen.
|
<!-- Missing file permission in search settings screen.
|
||||||
@ -372,6 +374,8 @@
|
|||||||
<string name="missing_permission_files_search">Grant storage permission to search photos, media and document on this device.</string>
|
<string name="missing_permission_files_search">Grant storage permission to search photos, media and document on this device.</string>
|
||||||
<!-- Missing files permission, used in contact search results -->
|
<!-- Missing files permission, used in contact search results -->
|
||||||
<string name="missing_permission_contact_search">Grant contact permission to search your contact.</string>
|
<string name="missing_permission_contact_search">Grant contact permission to search your contact.</string>
|
||||||
|
<!-- Missing permission app shortcuts permission, used in app shortcut search results. %1$s: app name -->
|
||||||
|
<string name="missing_permission_appshortcuts_search">Set %1$s as default home app to search app shortcuts.</string>
|
||||||
<!-- Grant a permission, shown in permission banners -->
|
<!-- Grant a permission, shown in permission banners -->
|
||||||
<string name="grant_permission">Grant</string>
|
<string name="grant_permission">Grant</string>
|
||||||
<!-- Appearance preference title -->
|
<!-- Appearance preference title -->
|
||||||
@ -499,6 +503,8 @@
|
|||||||
<string name="preference_search_contacts_summary">Search contacts on this device</string>
|
<string name="preference_search_contacts_summary">Search contacts on this device</string>
|
||||||
<string name="preference_search_calendar">Calendar</string>
|
<string name="preference_search_calendar">Calendar</string>
|
||||||
<string name="preference_search_calendar_summary">Search upcoming appointments and events</string>
|
<string name="preference_search_calendar_summary">Search upcoming appointments and events</string>
|
||||||
|
<string name="preference_search_appshortcuts">App shortcuts</string>
|
||||||
|
<string name="preference_search_appshortcuts_summary">Search app shortcuts</string>
|
||||||
<string name="preference_search_calculator">Calculator</string>
|
<string name="preference_search_calculator">Calculator</string>
|
||||||
<string name="preference_search_calculator_summary">Evaluate mathematical terms</string>
|
<string name="preference_search_calculator_summary">Evaluate mathematical terms</string>
|
||||||
<string name="preference_search_unitconverter">Unit converter</string>
|
<string name="preference_search_unitconverter">Unit converter</string>
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import de.mm20.launcher2.crashreporter.CrashReporter
|
|||||||
import de.mm20.launcher2.preferences.migrations.FactorySettingsMigration
|
import de.mm20.launcher2.preferences.migrations.FactorySettingsMigration
|
||||||
import de.mm20.launcher2.preferences.migrations.Migration_1_2
|
import de.mm20.launcher2.preferences.migrations.Migration_1_2
|
||||||
import de.mm20.launcher2.preferences.migrations.Migration_2_3
|
import de.mm20.launcher2.preferences.migrations.Migration_2_3
|
||||||
|
import de.mm20.launcher2.preferences.migrations.Migration_3_4
|
||||||
|
|
||||||
typealias LauncherDataStore = DataStore<Settings>
|
typealias LauncherDataStore = DataStore<Settings>
|
||||||
|
|
||||||
@ -19,7 +20,8 @@ internal val Context.dataStore: LauncherDataStore by dataStore(
|
|||||||
listOf(
|
listOf(
|
||||||
FactorySettingsMigration(it),
|
FactorySettingsMigration(it),
|
||||||
Migration_1_2(),
|
Migration_1_2(),
|
||||||
Migration_2_3()
|
Migration_2_3(),
|
||||||
|
Migration_3_4(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
corruptionHandler = ReplaceFileCorruptionHandler {
|
corruptionHandler = ReplaceFileCorruptionHandler {
|
||||||
@ -29,4 +31,4 @@ internal val Context.dataStore: LauncherDataStore by dataStore(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
internal const val SchemaVersion = 3
|
internal const val SchemaVersion = 4
|
||||||
@ -65,6 +65,11 @@ fun createFactorySettings(context: Context): Settings {
|
|||||||
.newBuilder()
|
.newBuilder()
|
||||||
.setEnabled(true)
|
.setEnabled(true)
|
||||||
)
|
)
|
||||||
|
.setAppShortcutSearch(
|
||||||
|
Settings.AppShortcutSearchSettings
|
||||||
|
.newBuilder()
|
||||||
|
.setEnabled(true)
|
||||||
|
)
|
||||||
.setCalculatorSearch(
|
.setCalculatorSearch(
|
||||||
Settings.CalculatorSearchSettings
|
Settings.CalculatorSearchSettings
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
package de.mm20.launcher2.preferences.migrations
|
||||||
|
|
||||||
|
import de.mm20.launcher2.preferences.Settings
|
||||||
|
|
||||||
|
class Migration_3_4: VersionedMigration(3, 4) {
|
||||||
|
override suspend fun applyMigrations(builder: Settings.Builder): Settings.Builder {
|
||||||
|
return builder.setAppShortcutSearch(
|
||||||
|
Settings.AppShortcutSearchSettings.newBuilder()
|
||||||
|
.setEnabled(true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -180,4 +180,9 @@ message Settings {
|
|||||||
uint32 border_width = 3;
|
uint32 border_width = 3;
|
||||||
}
|
}
|
||||||
CardSettings cards = 24;
|
CardSettings cards = 24;
|
||||||
|
|
||||||
|
message AppShortcutSearchSettings {
|
||||||
|
bool enabled = 1;
|
||||||
|
}
|
||||||
|
AppShortcutSearchSettings app_shortcut_search = 25;
|
||||||
}
|
}
|
||||||
@ -94,6 +94,28 @@ fun SearchSettingsScreen() {
|
|||||||
enabled = hasCalendarPermission == true
|
enabled = hasCalendarPermission == true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val hasAppShortcutsPermission by viewModel.hasAppShortcutPermission.observeAsState()
|
||||||
|
AnimatedVisibility(hasAppShortcutsPermission == false) {
|
||||||
|
MissingPermissionBanner(
|
||||||
|
text = stringResource(R.string.missing_permission_appshortcuts_search_settings, stringResource(R.string.app_name)),
|
||||||
|
onClick = {
|
||||||
|
viewModel.requestAppShortcutsPermission(context as AppCompatActivity)
|
||||||
|
},
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val appShortcuts by viewModel.appShortcuts.observeAsState()
|
||||||
|
SwitchPreference(
|
||||||
|
title = stringResource(R.string.preference_search_appshortcuts),
|
||||||
|
summary = stringResource(R.string.preference_search_appshortcuts_summary),
|
||||||
|
icon = Icons.Rounded.AppShortcut,
|
||||||
|
value = appShortcuts == true && hasAppShortcutsPermission == true,
|
||||||
|
onValueChanged = {
|
||||||
|
viewModel.setAppShortcuts(it)
|
||||||
|
},
|
||||||
|
enabled = hasAppShortcutsPermission == true
|
||||||
|
)
|
||||||
|
|
||||||
val calculator by viewModel.calculator.observeAsState()
|
val calculator by viewModel.calculator.observeAsState()
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
title = stringResource(R.string.preference_search_calculator),
|
title = stringResource(R.string.preference_search_calculator),
|
||||||
|
|||||||
@ -150,4 +150,23 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val hasAppShortcutPermission = permissionsManager.hasPermission(PermissionGroup.AppShortcuts).asLiveData()
|
||||||
|
val appShortcuts = dataStore.data.map { it.appShortcutSearch.enabled }.asLiveData()
|
||||||
|
fun setAppShortcuts(appShortcuts: Boolean) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
dataStore.updateData {
|
||||||
|
it.toBuilder()
|
||||||
|
.setAppShortcutSearch(
|
||||||
|
it.appShortcutSearch.toBuilder()
|
||||||
|
.setEnabled(appShortcuts)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun requestAppShortcutsPermission(activity: AppCompatActivity) {
|
||||||
|
permissionsManager.requestPermission(activity, PermissionGroup.AppShortcuts)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user