Add preferences for launch app gesture

This commit is contained in:
MM20 2023-02-24 22:39:32 +01:00
parent c611f9aa8e
commit 77e22e5f2b
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
7 changed files with 498 additions and 32 deletions

View File

@ -0,0 +1,114 @@
package de.mm20.launcher2.ui.common
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.CheckCircle
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
import de.mm20.launcher2.ui.ktx.toPixels
@Composable
fun SearchablePicker(
value: SavableSearchable?,
onValueChanged: (SavableSearchable?) -> Unit,
title: @Composable () -> Unit,
onDismissRequest: () -> Unit,
) {
val viewModel: SearchablePickerVM = viewModel()
BottomSheetDialog(onDismissRequest = onDismissRequest, title = title) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(it)
) {
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 12.dp),
value = viewModel.searchQuery,
onValueChange = { viewModel.onSearchQueryChanged(it) },
leadingIcon = {
Icon(Icons.Rounded.Search, null)
},
placeholder = {
Text(stringResource(R.string.search_bar_placeholder))
}
)
LazyColumn(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
) {
items(viewModel.items) {
val iconSize = 32.dp.toPixels()
val icon by remember(it.key) {
viewModel.getIcon(
it,
iconSize.toInt()
)
}.collectAsStateWithLifecycle(null)
val selected = it.key == value?.key
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
shape = MaterialTheme.shapes.small,
border = BorderStroke(1.dp, if (selected) MaterialTheme.colorScheme.secondary else MaterialTheme.colorScheme.outline),
color = if (selected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface,
onClick = { onValueChanged(it) }) {
Row(
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
ShapedLauncherIcon(
icon = { icon },
size = 32.dp,
)
Text(
text = it.label,
modifier = Modifier.weight(1f).padding(horizontal = 16.dp),
style = MaterialTheme.typography.labelMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (selected) {
Icon(
imageVector = Icons.Rounded.CheckCircle,
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary,
)
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,66 @@
package de.mm20.launcher2.ui.common
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.icons.IconRepository
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchService
import de.mm20.launcher2.search.toList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.androidx.compose.inject
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class SearchablePickerVM: ViewModel(), KoinComponent {
private val dataStore: LauncherDataStore by inject()
private val searchService: SearchService by inject()
private val iconRepository: IconRepository by inject()
var searchQuery by mutableStateOf("")
init {
onSearchQueryChanged("", true)
}
var items by mutableStateOf(emptyList<SavableSearchable>())
var searchJob: Job? = null
fun onSearchQueryChanged(query: String, forceRestart: Boolean = false) {
if (searchQuery == query && !forceRestart) return
searchQuery = query
searchJob?.cancel()
searchJob = viewModelScope.launch {
val settings = dataStore.data.first()
searchService.search(
query = query,
shortcuts = settings.appShortcutSearch,
contacts = settings.contactsSearch,
calendars = settings.calendarSearch,
files = settings.fileSearch,
).collectLatest {
if (searchQuery != query) return@collectLatest
items = withContext(Dispatchers.Default) {
it.toList().filterIsInstance<SavableSearchable>().sorted()
}
}
}
}
fun getIcon(searchable: SavableSearchable, size: Int): Flow<LauncherIcon> {
return iconRepository.getIcon(searchable, size)
}
}

View File

@ -2,23 +2,43 @@ package de.mm20.launcher2.ui.settings.gestures
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.preferences.Settings.GestureSettings.GestureAction
import de.mm20.launcher2.preferences.Settings.LayoutSettings.Layout
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.common.SearchablePicker
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
import de.mm20.launcher2.ui.component.preferences.ListPreference
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.ktx.toPixels
@Composable
fun GestureSettingsScreen() {
@ -35,11 +55,13 @@ fun GestureSettingsScreen() {
add(stringResource(R.string.gesture_action_recents) to GestureAction.OpenRecents)
add(stringResource(R.string.gesture_action_power_menu) to GestureAction.OpenPowerDialog)
add(stringResource(R.string.gesture_action_open_search) to GestureAction.OpenSearch)
add(stringResource(R.string.gesture_action_launch_app) to GestureAction.LaunchApp)
}
val context = LocalContext.current
PreferenceScreen(title = stringResource(R.string.preference_screen_gestures)) {
item {
val appIconSize = 32.dp.toPixels()
PreferenceCategory {
val doubleTap by viewModel.doubleTap.observeAsState()
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(doubleTap)) {
@ -49,12 +71,21 @@ fun GestureSettingsScreen() {
onClick = { viewModel.requestPermission(context as AppCompatActivity) }
)
}
ListPreference(
val doubleTapApp by viewModel.doubleTapApp.collectAsState(null)
val doubleTapAppIcon by remember(doubleTapApp?.key) {
viewModel.getIcon(doubleTapApp, appIconSize.toInt())
}.collectAsState(null)
GesturePreference(
title = stringResource(R.string.preference_gesture_double_tap),
items = options,
value = doubleTap,
onValueChanged = { if (it != null) viewModel.setDoubleTap(it) }
onValueChanged = { viewModel.setDoubleTap(it) },
isOpenSearch = false,
options = options,
app = doubleTapApp,
appIcon = doubleTapAppIcon,
onAppChanged = { viewModel.setDoubleTapApp(it) }
)
val longPress by viewModel.longPress.observeAsState()
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(longPress)) {
MissingPermissionBanner(
@ -63,12 +94,21 @@ fun GestureSettingsScreen() {
onClick = { viewModel.requestPermission(context as AppCompatActivity) }
)
}
ListPreference(
val longPressApp by viewModel.longPressApp.collectAsState(null)
val longPressAppIcon by remember(longPressApp?.key) {
viewModel.getIcon(longPressApp, appIconSize.toInt())
}.collectAsState(null)
GesturePreference(
title = stringResource(R.string.preference_gesture_long_press),
items = options,
value = longPress,
onValueChanged = { if (it != null) viewModel.setLongPress(it) }
onValueChanged = { viewModel.setLongPress(it) },
isOpenSearch = false,
options = options,
app = longPressApp,
appIcon = longPressAppIcon,
onAppChanged = { viewModel.setLongPressApp(it) }
)
val swipeDown by viewModel.swipeDown.observeAsState()
val swipeDownIsSearch = layout == Layout.PullDown
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(swipeDown) && !swipeDownIsSearch) {
@ -78,13 +118,21 @@ fun GestureSettingsScreen() {
onClick = { viewModel.requestPermission(context as AppCompatActivity) }
)
}
ListPreference(
val swipeDownApp by viewModel.swipeDownApp.collectAsState(null)
val swipeDownAppIcon by remember(swipeDownApp?.key) {
viewModel.getIcon(swipeDownApp, appIconSize.toInt())
}.collectAsState(null)
GesturePreference(
title = stringResource(R.string.preference_gesture_swipe_down),
enabled = !swipeDownIsSearch,
items = options,
value = if (swipeDownIsSearch) GestureAction.OpenSearch else swipeDown,
onValueChanged = { if (it != null) viewModel.setSwipeDown(it) }
value = swipeDown,
onValueChanged = { viewModel.setSwipeDown(it) },
isOpenSearch = swipeDownIsSearch,
options = options,
app = swipeDownApp,
appIcon = swipeDownAppIcon,
onAppChanged = { viewModel.setSwipeDownApp(it) }
)
val swipeLeft by viewModel.swipeLeft.observeAsState()
val swipeLeftIsSearch = layout == Layout.Pager
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(swipeLeft) && !swipeLeftIsSearch) {
@ -94,13 +142,21 @@ fun GestureSettingsScreen() {
onClick = { viewModel.requestPermission(context as AppCompatActivity) }
)
}
ListPreference(
val swipeLeftApp by viewModel.swipeLeftApp.collectAsState(null)
val swipeLeftAppIcon by remember(swipeLeftApp?.key) {
viewModel.getIcon(swipeLeftApp, appIconSize.toInt())
}.collectAsState(null)
GesturePreference(
title = stringResource(R.string.preference_gesture_swipe_left),
enabled = !swipeLeftIsSearch,
items = options,
value = if (swipeLeftIsSearch) GestureAction.OpenSearch else swipeLeft,
onValueChanged = { if (it != null) viewModel.setSwipeLeft(it) }
value = swipeLeft,
onValueChanged = { viewModel.setSwipeLeft(it) },
isOpenSearch = swipeLeftIsSearch,
options = options,
app = swipeLeftApp,
appIcon = swipeLeftAppIcon,
onAppChanged = { viewModel.setSwipeLeftApp(it) }
)
val swipeRight by viewModel.swipeRight.observeAsState()
val swipeRightIsSearch = layout == Layout.PagerReversed
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(swipeRight) && !swipeRightIsSearch) {
@ -110,20 +166,27 @@ fun GestureSettingsScreen() {
onClick = { viewModel.requestPermission(context as AppCompatActivity) }
)
}
ListPreference(
val swipeRightApp by viewModel.swipeRightApp.collectAsState(null)
val swipeRightAppIcon by remember(swipeRightApp?.key) {
viewModel.getIcon(swipeRightApp, appIconSize.toInt())
}.collectAsState(null)
GesturePreference(
title = stringResource(R.string.preference_gesture_swipe_right),
enabled = !swipeRightIsSearch,
items = options,
value = if (swipeRightIsSearch) GestureAction.OpenSearch else swipeRight,
onValueChanged = { if (it != null) viewModel.setSwipeRight(it) }
value = swipeRight,
onValueChanged = { viewModel.setSwipeRight(it) },
isOpenSearch = swipeRightIsSearch,
options = options,
app = swipeRightApp,
appIcon = swipeRightAppIcon,
onAppChanged = { viewModel.setSwipeRightApp(it) }
)
}
}
}
}
fun requiresAccessibilityService(action: GestureAction?) : Boolean{
return when(action) {
fun requiresAccessibilityService(action: GestureAction?): Boolean {
return when (action) {
GestureAction.OpenNotificationDrawer,
GestureAction.LockScreen,
GestureAction.OpenQuickSettings,
@ -131,4 +194,64 @@ fun requiresAccessibilityService(action: GestureAction?) : Boolean{
GestureAction.OpenPowerDialog -> true
else -> false
}
}
@Composable
fun GesturePreference(
title: String,
value: GestureAction?,
onValueChanged: (GestureAction) -> Unit,
isOpenSearch: Boolean,
options: List<Pair<String, GestureAction>>,
app: SavableSearchable?,
appIcon: LauncherIcon?,
onAppChanged: (SavableSearchable?) -> Unit,
) {
var showAppPicker by remember { mutableStateOf(false) }
Row(
verticalAlignment = (Alignment.CenterVertically)
) {
Box(
modifier = Modifier.weight(1f)
) {
ListPreference(
title = title,
enabled = !isOpenSearch,
items = options,
value = if (isOpenSearch) GestureAction.OpenSearch else value,
onValueChanged = { if (it != null) onValueChanged(it) }
)
}
if (value == GestureAction.LaunchApp && !isOpenSearch) {
Box(
modifier = Modifier
.height(36.dp)
.width(1.dp)
.alpha(0.38f)
.background(LocalContentColor.current)
)
Box(modifier = Modifier
.clickable { showAppPicker = true }
.padding(12.dp)) {
ShapedLauncherIcon(size = 32.dp, icon = { appIcon })
}
}
}
if (!isOpenSearch && value == GestureAction.LaunchApp && (showAppPicker || app == null)) {
SearchablePicker(
title = { Text(title) },
onDismissRequest = {
showAppPicker = false
if (app == null) onValueChanged(GestureAction.None)
},
value = app,
onValueChanged = {
showAppPicker = false
onAppChanged(it)
}
)
}
}

View File

@ -4,11 +4,21 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.icons.IconRepository
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.Settings.GestureSettings.GestureAction
import de.mm20.launcher2.search.SavableSearchable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@ -16,6 +26,8 @@ import org.koin.core.component.inject
class GestureSettingsScreenVM : ViewModel(), KoinComponent {
private val dataStore: LauncherDataStore by inject()
private val permissionsManager: PermissionsManager by inject()
private val favoritesRepository: FavoritesRepository by inject()
private val iconRepository: IconRepository by inject()
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Accessibility).asLiveData()
@ -72,7 +84,114 @@ class GestureSettingsScreenVM : ViewModel(), KoinComponent {
}
}
val swipeLeftApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.swipeLeftApp }
.map {
if (it.isEmpty()) null else favoritesRepository.getFromKeys(listOf(it)).firstOrNull()
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
fun setSwipeLeftApp(searchable: SavableSearchable?) {
viewModelScope.launch {
searchable?.let { favoritesRepository.save(it) }
dataStore.updateData {
it.toBuilder()
.setGestures(it.gestures.toBuilder()
.setSwipeLeftApp(searchable?.key ?: "")
.build()
)
.build()
}
}
}
val swipeRightApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.swipeRightApp }
.map {
if (it.isEmpty()) null else favoritesRepository.getFromKeys(listOf(it)).firstOrNull()
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
fun setSwipeRightApp(searchable: SavableSearchable?) {
viewModelScope.launch {
searchable?.let { favoritesRepository.save(it) }
dataStore.updateData {
it.toBuilder()
.setGestures(it.gestures.toBuilder()
.setSwipeRightApp(searchable?.key ?: "")
.build()
)
.build()
}
}
}
val swipeDownApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.swipeDownApp }
.map {
if (it.isEmpty()) null else favoritesRepository.getFromKeys(listOf(it)).firstOrNull()
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
fun setSwipeDownApp(searchable: SavableSearchable?) {
viewModelScope.launch {
searchable?.let { favoritesRepository.save(it) }
dataStore.updateData {
it.toBuilder()
.setGestures(it.gestures.toBuilder()
.setSwipeDownApp(searchable?.key ?: "")
.build()
)
.build()
}
}
}
val longPressApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.longPressApp }
.map {
if (it.isEmpty()) null else favoritesRepository.getFromKeys(listOf(it)).firstOrNull()
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
fun setLongPressApp(searchable: SavableSearchable?) {
viewModelScope.launch {
searchable?.let { favoritesRepository.save(it) }
dataStore.updateData {
it.toBuilder()
.setGestures(it.gestures.toBuilder()
.setLongPressApp(searchable?.key ?: "")
.build()
)
.build()
}
}
}
val doubleTapApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.doubleTapApp }
.map {
if (it.isEmpty()) null else favoritesRepository.getFromKeys(listOf(it)).firstOrNull()
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
fun setDoubleTapApp(searchable: SavableSearchable?) {
viewModelScope.launch {
searchable?.let { favoritesRepository.save(it) }
dataStore.updateData {
it.toBuilder()
.setGestures(it.gestures.toBuilder()
.setDoubleTapApp(searchable?.key ?: "")
.build()
)
.build()
}
}
}
fun requestPermission(context: AppCompatActivity) {
permissionsManager.requestPermission(context, PermissionGroup.Accessibility)
}
fun getIcon(searchable: SavableSearchable?, size: Int): Flow<LauncherIcon> {
if (searchable == null) return emptyFlow()
return iconRepository.getIcon(searchable, size)
}
}

View File

@ -750,6 +750,7 @@
<string name="preference_gesture_long_press">Long press</string>
<string name="gesture_action_none">None</string>
<string name="gesture_action_open_search">Open search</string>
<string name="gesture_action_launch_app">Launch app</string>
<string name="gesture_action_notifications">Open notification drawer</string>
<string name="gesture_action_lock_screen">Turn off screen</string>
<string name="gesture_action_quick_settings">Open quick settings</string>

View File

@ -313,12 +313,18 @@ message Settings {
OpenQuickSettings = 4;
OpenRecents = 5;
OpenPowerDialog = 6;
LaunchApp = 7;
}
GestureAction swipe_down = 1;
GestureAction swipe_left = 2;
GestureAction swipe_right = 3;
GestureAction double_tap = 4;
GestureAction long_press = 5;
string swipe_down_app = 6;
string swipe_left_app = 7;
string swipe_right_app = 8;
string double_tap_app = 9;
string long_press_app = 10;
}
GestureSettings gestures = 28;
}

View File

@ -8,6 +8,7 @@ import de.mm20.launcher2.contacts.ContactRepository
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
import de.mm20.launcher2.data.customattrs.utils.withCustomLabels
import de.mm20.launcher2.files.FileRepository
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.preferences.Settings.AppShortcutSearchSettings
import de.mm20.launcher2.preferences.Settings.CalculatorSearchSettings
import de.mm20.launcher2.preferences.Settings.CalendarSearchSettings
@ -50,14 +51,34 @@ import kotlinx.coroutines.supervisorScope
interface SearchService {
fun search(
query: String,
shortcuts: AppShortcutSearchSettings,
contacts: ContactsSearchSettings,
calendars: CalendarSearchSettings,
files: FilesSearchSettings,
calculator: CalculatorSearchSettings,
unitConverter: UnitConverterSearchSettings,
websites: WebsiteSearchSettings,
wikipedia: WikipediaSearchSettings,
shortcuts: AppShortcutSearchSettings = Settings.AppShortcutSearchSettings.newBuilder()
.setEnabled(false)
.build(),
contacts: ContactsSearchSettings = Settings.ContactsSearchSettings.newBuilder()
.setEnabled(false)
.build(),
calendars: CalendarSearchSettings = Settings.CalendarSearchSettings.newBuilder()
.setEnabled(false)
.build(),
files: FilesSearchSettings = Settings.FilesSearchSettings.newBuilder()
.setLocalFiles(false)
.setGdrive(false)
.setOnedrive(false)
.setOwncloud(false)
.setNextcloud(false)
.build(),
calculator: CalculatorSearchSettings = Settings.CalculatorSearchSettings.newBuilder()
.setEnabled(false)
.build(),
unitConverter: UnitConverterSearchSettings = Settings.UnitConverterSearchSettings.newBuilder()
.setEnabled(false)
.build(),
websites: WebsiteSearchSettings = Settings.WebsiteSearchSettings.newBuilder()
.setEnabled(false)
.build(),
wikipedia: WikipediaSearchSettings = Settings.WikipediaSearchSettings.newBuilder()
.setEnabled(false)
.build(),
): Flow<SearchResults>
}
@ -245,4 +266,20 @@ data class SearchResults(
val wikipedia: ImmutableList<Wikipedia>? = null,
val searchActions: ImmutableList<SearchAction>? = null,
val other: ImmutableList<SavableSearchable>? = null,
)
)
fun SearchResults.toList(): List<Searchable> {
return listOfNotNull(
apps,
shortcuts,
contacts,
calendars,
files,
calculators,
unitConverters,
websites,
wikipedia,
searchActions,
other,
).flatten()
}