Add preferences for app shortcut search

This commit is contained in:
MM20 2022-03-21 14:58:14 +01:00
parent a4a562e335
commit d6ebbd193a
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
10 changed files with 129 additions and 41 deletions

View File

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

View File

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

View File

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

View File

@ -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>

View File

@ -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

View File

@ -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()

View File

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

View File

@ -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;
} }

View File

@ -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),

View File

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