From d6ebbd193a4f05cefd036302435d0f0abb30a58c Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Mon, 21 Mar 2022 14:58:14 +0100 Subject: [PATCH] Add preferences for app shortcut search --- appshortcuts/build.gradle.kts | 1 + .../appshortcuts/AppShortcutRepository.kt | 92 +++++++++++-------- .../de/mm20/launcher2/appshortcuts/Module.kt | 2 +- i18n/src/main/res/values/strings.xml | 6 ++ .../mm20/launcher2/preferences/DataStore.kt | 6 +- .../de/mm20/launcher2/preferences/Defaults.kt | 5 + .../preferences/migrations/Migration_3_4.kt | 12 +++ preferences/src/main/proto/settings.proto | 5 + .../settings/search/SearchSettingsScreen.kt | 22 +++++ .../settings/search/SearchSettingsScreenVM.kt | 19 ++++ 10 files changed, 129 insertions(+), 41 deletions(-) create mode 100644 preferences/src/main/java/de/mm20/launcher2/preferences/migrations/Migration_3_4.kt diff --git a/appshortcuts/build.gradle.kts b/appshortcuts/build.gradle.kts index 8dee88cf..56ca570c 100644 --- a/appshortcuts/build.gradle.kts +++ b/appshortcuts/build.gradle.kts @@ -44,6 +44,7 @@ dependencies { implementation(libs.tinypinyin) implementation(project(":search")) + implementation(project(":permissions")) implementation(project(":base")) implementation(project(":preferences")) implementation(project(":ktx")) diff --git a/appshortcuts/src/main/java/de/mm20/launcher2/appshortcuts/AppShortcutRepository.kt b/appshortcuts/src/main/java/de/mm20/launcher2/appshortcuts/AppShortcutRepository.kt index 166d8df5..b2bd6b80 100644 --- a/appshortcuts/src/main/java/de/mm20/launcher2/appshortcuts/AppShortcutRepository.kt +++ b/appshortcuts/src/main/java/de/mm20/launcher2/appshortcuts/AppShortcutRepository.kt @@ -7,10 +7,14 @@ import android.content.pm.PackageManager import android.os.Process import androidx.core.content.getSystemService 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 kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext import org.apache.commons.text.similarity.FuzzyScore import java.util.* @@ -25,7 +29,9 @@ interface AppShortcutRepository { } internal class AppShortcutRepositoryImpl( - private val context: Context + private val context: Context, + private val permissionsManager: PermissionsManager, + private val dataStore: LauncherDataStore, ) : AppShortcutRepository { override suspend fun getShortcutsForActivity( @@ -59,46 +65,56 @@ internal class AppShortcutRepositoryImpl( } override fun search(query: String) = channelFlow> { - val launcherApps = context.getSystemService() ?: 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) { - "" + dataStore.data.map { it.appShortcutSearch.enabled }.collectLatest { enabled -> + if (!enabled) { + send(emptyList()) + return@collectLatest } - AppShortcut( - context, - it, - label + + if (query.length < 3) { + return@collectLatest send(emptyList()) + } + + val launcherApps = + context.getSystemService() ?: 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()) + } } } diff --git a/appshortcuts/src/main/java/de/mm20/launcher2/appshortcuts/Module.kt b/appshortcuts/src/main/java/de/mm20/launcher2/appshortcuts/Module.kt index 17fa8371..e3b727dd 100644 --- a/appshortcuts/src/main/java/de/mm20/launcher2/appshortcuts/Module.kt +++ b/appshortcuts/src/main/java/de/mm20/launcher2/appshortcuts/Module.kt @@ -4,5 +4,5 @@ import org.koin.android.ext.koin.androidContext import org.koin.dsl.module val appShortcutsModule = module { - single { AppShortcutRepositoryImpl(androidContext()) } + single { AppShortcutRepositoryImpl(androidContext(), get(), get()) } } \ No newline at end of file diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 2be04b73..0d8b3261 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -356,6 +356,8 @@ Calendar permission is required to search calendar This widget requires calendar permission + + %1$s must be set as default home app to search app shortcuts External storage permission is required to search local files Grant contact permission to search your contact. + + Set %1$s as default home app to search app shortcuts. Grant @@ -499,6 +503,8 @@ Search contacts on this device Calendar Search upcoming appointments and events + App shortcuts + Search app shortcuts Calculator Evaluate mathematical terms Unit converter diff --git a/preferences/src/main/java/de/mm20/launcher2/preferences/DataStore.kt b/preferences/src/main/java/de/mm20/launcher2/preferences/DataStore.kt index 2b1f36d5..01ca27ec 100644 --- a/preferences/src/main/java/de/mm20/launcher2/preferences/DataStore.kt +++ b/preferences/src/main/java/de/mm20/launcher2/preferences/DataStore.kt @@ -9,6 +9,7 @@ import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.preferences.migrations.FactorySettingsMigration 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_3_4 typealias LauncherDataStore = DataStore @@ -19,7 +20,8 @@ internal val Context.dataStore: LauncherDataStore by dataStore( listOf( FactorySettingsMigration(it), Migration_1_2(), - Migration_2_3() + Migration_2_3(), + Migration_3_4(), ) }, corruptionHandler = ReplaceFileCorruptionHandler { @@ -29,4 +31,4 @@ internal val Context.dataStore: LauncherDataStore by dataStore( } ) -internal const val SchemaVersion = 3 \ No newline at end of file +internal const val SchemaVersion = 4 \ No newline at end of file diff --git a/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt b/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt index abec5b9a..5e9d5bb1 100644 --- a/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt +++ b/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt @@ -65,6 +65,11 @@ fun createFactorySettings(context: Context): Settings { .newBuilder() .setEnabled(true) ) + .setAppShortcutSearch( + Settings.AppShortcutSearchSettings + .newBuilder() + .setEnabled(true) + ) .setCalculatorSearch( Settings.CalculatorSearchSettings .newBuilder() diff --git a/preferences/src/main/java/de/mm20/launcher2/preferences/migrations/Migration_3_4.kt b/preferences/src/main/java/de/mm20/launcher2/preferences/migrations/Migration_3_4.kt new file mode 100644 index 00000000..9cfb4f62 --- /dev/null +++ b/preferences/src/main/java/de/mm20/launcher2/preferences/migrations/Migration_3_4.kt @@ -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) + ) + } +} \ No newline at end of file diff --git a/preferences/src/main/proto/settings.proto b/preferences/src/main/proto/settings.proto index fb7a3021..ac608ee8 100644 --- a/preferences/src/main/proto/settings.proto +++ b/preferences/src/main/proto/settings.proto @@ -180,4 +180,9 @@ message Settings { uint32 border_width = 3; } CardSettings cards = 24; + + message AppShortcutSearchSettings { + bool enabled = 1; + } + AppShortcutSearchSettings app_shortcut_search = 25; } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreen.kt index 9021240e..1cefde45 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreen.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreen.kt @@ -94,6 +94,28 @@ fun SearchSettingsScreen() { 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() SwitchPreference( title = stringResource(R.string.preference_search_calculator), diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreenVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreenVM.kt index 62720633..7f676256 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreenVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreenVM.kt @@ -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) + } } \ No newline at end of file