Add preferences for launch app gesture
This commit is contained in:
parent
c611f9aa8e
commit
77e22e5f2b
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user