Searchbar: Launch on enter / done (#258)

* Search: add launch action on keyboard-done

* Add switch in settings

* :)

* fix dataStore-update, pull bestMatch from multiple sources

* launch searchAction when no other match is found

* update localization

* switch to .firstNotNullOfOrNull { }

* switch imeAction based on launchOnEnter == true

* localization

* adding highlighting for bestMatch

* Remove unused code

* Store hightlighted search result in viewmodel

* Add highlight for list item, tweak grid item highlight

* Highlight search action that is launched on enter

* localizaton

* switch to IconShape / hairline border

* remove border / outlineVariant-background

---------

Co-authored-by: MM20 <15646950+MM2-0@users.noreply.github.com>
This commit is contained in:
Christoph 2023-02-19 18:51:31 +01:00 committed by GitHub
parent 72b42a1105
commit b11ba9e23a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 298 additions and 405 deletions

View File

@ -21,6 +21,7 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.searchactions.actions.SearchAction
import de.mm20.launcher2.ui.component.SearchBarLevel
import de.mm20.launcher2.ui.launcher.LauncherScaffoldVM
import de.mm20.launcher2.ui.launcher.helper.WallpaperBlur
@ -199,6 +200,7 @@ fun AssistantScaffold(
viewModel.setSearchbarFocus(it)
},
actions = actions,
highlightedAction = searchVM.bestMatch.value as? SearchAction,
showHiddenItemsButton = true,
value = { value },
onValueChange = { searchVM.search(it) },

View File

@ -8,11 +8,9 @@ import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.LocalAbsoluteTonalElevation
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
@ -24,41 +22,56 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.ui.locals.LocalCardStyle
import kotlin.math.ln
@Composable
fun InnerCard(
modifier: Modifier = Modifier,
raised: Boolean = false,
highlight: Boolean = false,
content: @Composable () -> Unit
) {
val transition = updateTransition(raised, label = "InnerCard")
val transition = updateTransition(InnerCardStyle(raised, highlight), label = "InnerCard")
val absoluteTonalElevation = LocalAbsoluteTonalElevation.current
val elevation by transition.animateDp(label = "elevation", transitionSpec = {
tween(250, if (targetState) 250 else 0)
tween(250, if (targetState == InnerCardStyle.Raised) 250 else 0)
}) {
if(it) 4.dp else 0.dp
if (it == InnerCardStyle.Raised) 2.dp else 0.dp
}
val borderWidth by transition.animateDp(label = "borderWidth", transitionSpec = { tween(500) }) {
if (it) 0.dp else 1.dp
val borderWidth by transition.animateDp(
label = "borderWidth",
transitionSpec = { tween(500) }) {
when (it) {
InnerCardStyle.Raised -> 0.dp
InnerCardStyle.Highlighted -> 1.dp
InnerCardStyle.Default -> 1.dp
}
}
val borderColor by transition.animateColor(label = "borderColor", transitionSpec = { tween(500) }) {
MaterialTheme.colorScheme.outline.copy(alpha = if (it) 0f else 0.7f)
val borderColor by transition.animateColor(
label = "borderColor",
transitionSpec = { tween(500) }) {
when (it) {
InnerCardStyle.Raised -> Color.Transparent
InnerCardStyle.Highlighted -> MaterialTheme.colorScheme.secondary
InnerCardStyle.Default -> MaterialTheme.colorScheme.outlineVariant
}
}
val bgAlpha by transition.animateFloat(label = "bgAlpha", transitionSpec = {
tween(250, if (targetState) 0 else 250)
val bgColor by transition.animateColor(label = "bgColor", transitionSpec = {
tween(250, if (targetState == InnerCardStyle.Raised) 0 else 250)
}) {
if (it) 1f else 0f
if (it == InnerCardStyle.Highlighted) {
MaterialTheme.colorScheme.secondaryContainer
} else {
MaterialTheme.colorScheme.surfaceColorAtElevation(absoluteTonalElevation + elevation)
}
}
val shape = MaterialTheme.shapes.small
val bgColor = MaterialTheme.colorScheme.surfaceColorAtElevation(absoluteTonalElevation + elevation)
Box(
modifier = modifier
.border(BorderStroke(borderWidth, borderColor), shape)
@ -66,10 +79,9 @@ fun InnerCard(
.clip(shape)
.drawBehind {
drawRect(
bgColor.copy(alpha = bgAlpha)
bgColor
)
}
,
},
) {
CompositionLocalProvider(
LocalAbsoluteTonalElevation provides absoluteTonalElevation
@ -79,6 +91,20 @@ fun InnerCard(
}
}
internal enum class InnerCardStyle {
Default,
Highlighted,
Raised,
}
internal fun InnerCardStyle(raised: Boolean, highlight: Boolean): InnerCardStyle {
return when {
raised -> InnerCardStyle.Raised
highlight -> InnerCardStyle.Highlighted
else -> InnerCardStyle.Default
}
}
internal fun ColorScheme.surfaceColorAtElevation(
elevation: Dp,
): Color {

View File

@ -15,6 +15,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActionScope
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
@ -34,6 +37,7 @@ import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.ui.R
@ -54,6 +58,7 @@ fun SearchBar(
darkColors: Boolean = false,
menu: @Composable RowScope.() -> Unit = {},
actions: @Composable ColumnScope.() -> Unit = {},
onKeyboardActionGo: (KeyboardActionScope.() -> Unit)? = null
) {
val transition = updateTransition(level, label = "Searchbar")
@ -171,7 +176,17 @@ fun SearchBar(
singleLine = true,
value = value,
onValueChange = onValueChange,
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary)
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
keyboardOptions = KeyboardOptions(
imeAction = if (onKeyboardActionGo == null) {
ImeAction.Search
} else {
ImeAction.Go
}
),
keyboardActions = KeyboardActions(
onGo = onKeyboardActionGo
)
)
}
Row(

View File

@ -228,13 +228,6 @@ fun ShapedLauncherIcon(
}
}
@Composable
private fun Badge(
badge: () -> Badge?
) {
}
@Composable
private fun IconLayer(
layer: LauncherIconLayer,

View File

@ -56,6 +56,8 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
val statusBarColor = dataStore.data.map { it.systemBars.statusBarColor }.asLiveData()
val navBarColor = dataStore.data.map { it.systemBars.statusBarColor }.asLiveData()
val launchOnEnter = dataStore.data.map { it.searchBar.launchOnEnter }.asLiveData()
val hideNavBar = dataStore.data.map { it.systemBars.hideNavBar }.asLiveData()
val hideStatusBar = dataStore.data.map { it.systemBars.hideStatusBar }.asLiveData()

View File

@ -69,6 +69,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import de.mm20.launcher2.preferences.Settings.SearchBarSettings.SearchBarColors
import de.mm20.launcher2.preferences.Settings.SearchBarSettings.SearchBarStyle
import de.mm20.launcher2.searchactions.actions.SearchAction
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.SearchBarLevel
import de.mm20.launcher2.ui.gestures.LocalGestureDetector
@ -503,6 +504,7 @@ fun PagerScaffold(
viewModel.setSearchbarFocus(it)
},
actions = actions,
highlightedAction = searchVM.bestMatch.value as? SearchAction,
showHiddenItemsButton = isSearchOpen,
value = { value },
onValueChange = { searchVM.search(it) },

View File

@ -56,6 +56,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
@ -66,6 +67,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.searchactions.actions.SearchAction
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.SearchBarLevel
import de.mm20.launcher2.ui.gestures.LocalGestureDetector
@ -89,6 +91,7 @@ fun PullDownScaffold(
bottomSearchBar: Boolean = false,
reverseSearchResults: Boolean = false,
fixedSearchBar: Boolean = false,
launchOnEnter: Boolean = false
) {
val viewModel: LauncherScaffoldVM = viewModel()
val searchVM: SearchVM = viewModel()
@ -394,9 +397,13 @@ fun PullDownScaffold(
}
.pointerInput(gestureManager.shouldDetectDoubleTaps) {
detectTapGestures(
onDoubleTap = if (gestureManager.shouldDetectDoubleTaps) {{
if (!isWidgetEditMode) gestureManager.dispatchDoubleTap(it)
}} else null,
onDoubleTap = if (gestureManager.shouldDetectDoubleTaps) {
{
if (!isWidgetEditMode) gestureManager.dispatchDoubleTap(
it
)
}
} else null,
onLongPress = {
if (!isWidgetEditMode) gestureManager.dispatchLongPress(
it
@ -518,6 +525,8 @@ fun PullDownScaffold(
val searchBarColor by viewModel.searchBarColor.observeAsState(Settings.SearchBarSettings.SearchBarColors.Auto)
val searchBarStyle by viewModel.searchBarStyle.observeAsState(Settings.SearchBarSettings.SearchBarStyle.Transparent)
val context = LocalContext.current
LauncherSearchBar(
modifier = Modifier
.align(if (bottomSearchBar) Alignment.BottomCenter else Alignment.TopCenter)
@ -547,12 +556,16 @@ fun PullDownScaffold(
viewModel.setSearchbarFocus(it)
},
actions = actions,
highlightedAction = searchVM.bestMatch.value as? SearchAction,
showHiddenItemsButton = isSearchOpen,
value = { value },
onValueChange = { searchVM.search(it) },
darkColors = LocalPreferDarkContentOverWallpaper.current && searchBarColor == Settings.SearchBarSettings.SearchBarColors.Auto || searchBarColor == Settings.SearchBarSettings.SearchBarColors.Dark,
style = searchBarStyle,
reverse = bottomSearchBar,
onKeyboardActionGo = if (launchOnEnter) {
{ searchVM.launchBestMatchOrAction(context) }
} else null
)
}

View File

@ -194,6 +194,7 @@ abstract class SharedLauncherActivity(
when (layout) {
Settings.LayoutSettings.Layout.PullDown -> {
key(bottomSearchBar, reverseSearchResults) {
val launchOnEnter by viewModel.launchOnEnter.observeAsState(false)
PullDownScaffold(
modifier = Modifier
.fillMaxSize()
@ -209,6 +210,7 @@ abstract class SharedLauncherActivity(
bottomSearchBar = bottomSearchBar,
reverseSearchResults = reverseSearchResults,
fixedSearchBar = fixedSearchBar,
launchOnEnter = launchOnEnter
)
}
}

View File

@ -94,6 +94,8 @@ fun SearchColumn(
val website by viewModel.websiteResults.observeAsState(emptyList())
val hiddenResults by viewModel.hiddenResults.observeAsState(emptyList())
val bestMatch by viewModel.bestMatch
val isSearchEmpty by viewModel.isSearchEmpty.observeAsState(true)
val missingCalendarPermission by viewModel.missingCalendarPermission.collectAsState(false)
@ -191,7 +193,8 @@ fun SearchColumn(
}
}
}
}
},
highlightedItem = bestMatch as? SavableSearchable
)
}
GridResults(
@ -241,7 +244,8 @@ fun SearchColumn(
)
}
}
} else null
} else null,
highlightedItem = bestMatch as? SavableSearchable
)
ListResults(
before = if (missingShortcutsPermission && !isSearchEmpty) {
@ -267,7 +271,8 @@ fun SearchColumn(
} else null,
items = appShortcuts.toImmutableList(),
reverse = reverse,
key = "shortcuts"
key = "shortcuts",
highlightedItem = bestMatch as? SavableSearchable
)
for (conv in unitConverter) {
SingleResult {
@ -300,7 +305,8 @@ fun SearchColumn(
} else null,
items = events.toImmutableList(),
reverse = reverse,
key = "events"
key = "events",
highlightedItem = bestMatch as? SavableSearchable
)
ListResults(
before = if (missingContactsPermission && !isSearchEmpty) {
@ -323,7 +329,8 @@ fun SearchColumn(
} else null,
items = contacts.toImmutableList(),
reverse = reverse,
key = "contacts"
key = "contacts",
highlightedItem = bestMatch as? SavableSearchable
)
for (wiki in wikipedia) {
SingleResult {
@ -356,7 +363,8 @@ fun SearchColumn(
} else null,
items = files.toImmutableList(),
reverse = reverse,
key = "files"
key = "files",
highlightedItem = bestMatch as? SavableSearchable
)
}
@ -373,6 +381,7 @@ fun LazyListScope.GridResults(
key: String,
before: (@Composable () -> Unit)? = null,
after: (@Composable () -> Unit)? = null,
highlightedItem: SavableSearchable?
) {
if (items.isEmpty() && before == null && after == null) return
@ -410,6 +419,7 @@ fun LazyListScope.GridResults(
(it * columns + columns).coerceAtMost(items.size)
),
columns = columns,
highlightedItem = highlightedItem
)
}
}
@ -433,6 +443,7 @@ fun GridRow(
items: ImmutableList<SavableSearchable>,
columns: Int,
showLabels: Boolean = LocalGridSettings.current.showLabels,
highlightedItem: SavableSearchable?
) {
Row(
@ -442,9 +453,10 @@ fun GridRow(
GridItem(
modifier = Modifier
.weight(1f)
.padding(4.dp, 8.dp),
.padding(4.dp),
item = item,
showLabels = showLabels
showLabels = showLabels,
highlight = item.key == highlightedItem?.key
)
}
for (i in 0 until columns - items.size) {
@ -459,6 +471,7 @@ fun LazyListScope.ListResults(
key: String,
before: (@Composable () -> Unit)? = null,
after: (@Composable () -> Unit)? = null,
highlightedItem: SavableSearchable?
) {
if (before != null) {
item(key = "$key-before") {
@ -488,6 +501,7 @@ fun LazyListScope.ListResults(
bottom = if (if (reverse) it == 0 else it == items.size - 1) 8.dp else 4.dp,
),
item = items[it],
highlight = items[it].key == highlightedItem?.key
)
}
}
@ -508,6 +522,7 @@ fun LazyListScope.ListResults(
fun ListRow(
modifier: Modifier = Modifier,
item: SavableSearchable,
highlight: Boolean
) {
Box(
modifier = modifier.padding(
@ -518,7 +533,8 @@ fun ListRow(
ListItem(
modifier = Modifier
.fillMaxWidth(),
item = item
item = item,
highlight = highlight
)
}
}

View File

@ -1,8 +1,14 @@
package de.mm20.launcher2.ui.launcher.search
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.map
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.permissions.PermissionGroup
@ -10,6 +16,7 @@ import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchService
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.search.data.Calculator
import de.mm20.launcher2.search.data.CalendarEvent
@ -22,12 +29,18 @@ import de.mm20.launcher2.search.data.Wikipedia
import de.mm20.launcher2.searchactions.actions.SearchAction
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
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
@ -38,10 +51,12 @@ class SearchVM : ViewModel(), KoinComponent {
private val permissionsManager: PermissionsManager by inject()
private val dataStore: LauncherDataStore by inject()
private val launchOnEnter = dataStore.data.map { it.searchBar.launchOnEnter }
.stateIn(viewModelScope, SharingStarted.Eagerly, false)
private val searchService: SearchService by inject()
val isSearching = MutableLiveData(false)
val searchQuery = MutableLiveData<String>("")
val searchQuery = MutableLiveData("")
val isSearchEmpty = MutableLiveData(true)
val appResults = MutableLiveData<List<LauncherApp>>(emptyList())
@ -63,18 +78,32 @@ class SearchVM : ViewModel(), KoinComponent {
private val hiddenItemKeys = favoritesRepository
.getHiddenItemKeys()
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), replay = 1)
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
val bestMatch = mutableStateOf<Searchable?>(null)
init {
search("", true)
}
var searchJob: Job? = null
fun launchBestMatchOrAction(context: Context) {
val bestMatch = bestMatch.value
if (bestMatch is SavableSearchable) {
bestMatch.launch(context, null)
return
} else if (bestMatch is SearchAction) {
bestMatch.start(context)
return
}
}
private var searchJob: Job? = null
fun search(query: String, forceRestart: Boolean = false) {
if (searchQuery.value == query && !forceRestart) return
searchQuery.value = query
isSearchEmpty.value = query.isEmpty()
hiddenResults.value = emptyList()
bestMatch.value = null
try {
searchJob?.cancel()
@ -82,7 +111,6 @@ class SearchVM : ViewModel(), KoinComponent {
}
hideFavorites.postValue(query.isNotEmpty())
searchJob = viewModelScope.launch {
isSearching.postValue(true)
dataStore.data.collectLatest {
searchService.search(
@ -128,6 +156,20 @@ class SearchVM : ViewModel(), KoinComponent {
r is SearchAction -> actions.add(r)
}
}
if (query.isNotEmpty() && launchOnEnter.value) {
bestMatch.value = listOf(
apps,
workApps,
shortcuts,
files,
contacts,
events,
wikipedia,
website,
actions
).firstNotNullOfOrNull { it.firstOrNull() }
}
searchActionResults.value = actions
appResults.value = apps
workAppResults.value = workApps
@ -143,9 +185,6 @@ class SearchVM : ViewModel(), KoinComponent {
}
}
}
isSearching.postValue(false)
}
}
@ -168,7 +207,6 @@ class SearchVM : ViewModel(), KoinComponent {
}
}
val missingContactsPermission = combine(
permissionsManager.hasPermission(PermissionGroup.Contacts),
dataStore.data.map { it.contactsSearch.enabled }.distinctUntilChanged()

View File

@ -1,30 +0,0 @@
package de.mm20.launcher2.ui.launcher.search.apps
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
@Composable
fun AppResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val apps by viewModel.appResults.observeAsState(emptyList())
if (apps.isNotEmpty()) {
LauncherCard(
modifier = Modifier
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
) {
SearchResultGrid(items = apps, reverse = reverse)
}
}
}

View File

@ -1,69 +0,0 @@
package de.mm20.launcher2.ui.launcher.search.appshortcuts
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
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.ui.R
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList
@Composable
fun ColumnScope.AppShortcutResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val shortcuts by viewModel.appShortcutResults.observeAsState(emptyList())
val context = LocalContext.current
val isSearchEmpty by viewModel.isSearchEmpty.observeAsState(true)
val missingPermission by viewModel.missingAppShortcutPermission.collectAsState(false)
AnimatedVisibility(shortcuts.isNotEmpty() || (!isSearchEmpty && missingPermission)) {
LauncherCard(
modifier = Modifier
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
) {
Column {
AnimatedVisibility(!isSearchEmpty && missingPermission) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_appshortcuts_search, stringResource(R.string.app_name)),
onClick = { viewModel.requestAppShortcutPermission(context as AppCompatActivity) },
modifier = Modifier.padding(16.dp),
secondaryAction = {
OutlinedButton(onClick = {
viewModel.disableAppShortcutSearch()
}) {
Text(
stringResource(R.string.turn_off),
)
}
}
)
}
}
SearchResultList(
items = shortcuts,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
reverse = reverse
)
}
}
}

View File

@ -1,69 +0,0 @@
package de.mm20.launcher2.ui.launcher.search.calendar
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
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.ui.R
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList
@Composable
fun ColumnScope.CalendarResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val calendarEvents by viewModel.calendarResults.observeAsState(emptyList())
val context = LocalContext.current
val isSearchEmpty by viewModel.isSearchEmpty.observeAsState(true)
val missingPermission by viewModel.missingCalendarPermission.collectAsState(false)
AnimatedVisibility(calendarEvents.isNotEmpty() || (!isSearchEmpty && missingPermission)) {
LauncherCard(
modifier = Modifier
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
.fillMaxWidth()
) {
Column {
AnimatedVisibility(!isSearchEmpty && missingPermission) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_calendar_search),
onClick = { viewModel.requestCalendarPermission(context as AppCompatActivity) },
modifier = Modifier.padding(16.dp),
secondaryAction = {
OutlinedButton(onClick = {
viewModel.disableCalendarSearch()
}) {
Text(
stringResource(R.string.turn_off),
)
}
}
)
}
SearchResultList(
items = calendarEvents,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
reverse = reverse
)
}
}
}
}

View File

@ -4,13 +4,28 @@ import android.content.ComponentName
import androidx.activity.compose.BackHandler
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.absoluteOffset
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
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.geometry.Rect
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
@ -18,14 +33,22 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.*
import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.search.data.CalendarEvent
import de.mm20.launcher2.search.data.Contact
import de.mm20.launcher2.search.data.File
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.data.Website
import de.mm20.launcher2.search.data.Wikipedia
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.component.LocalIconShape
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
import de.mm20.launcher2.ui.ktx.toDp
import de.mm20.launcher2.ui.ktx.toPixels
@ -45,13 +68,19 @@ import kotlinx.coroutines.delay
@Composable
fun GridItem(modifier: Modifier = Modifier, item: SavableSearchable, showLabels: Boolean = true) {
fun GridItem(
modifier: Modifier = Modifier,
item: SavableSearchable,
showLabels: Boolean = true,
highlight: Boolean = false
) {
val viewModel = remember(item.key) { GridItemVM(item) }
val context = LocalContext.current
var showPopup by remember(item.key) { mutableStateOf(false) }
var bounds by remember { mutableStateOf(Rect.Zero) }
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
val badge by remember(item.key) { viewModel.badge }.collectAsState(null)
val iconSize = LocalGridSettings.current.iconSize.dp.toPixels()
@ -72,7 +101,9 @@ fun GridItem(modifier: Modifier = Modifier, item: SavableSearchable, showLabels:
return@HandleHomeTransition HomeTransitionParams(
bounds
) { _, _ ->
ShapedLauncherIcon(size = LocalGridSettings.current.iconSize.dp, icon = { icon })
ShapedLauncherIcon(
size = LocalGridSettings.current.iconSize.dp,
icon = { icon })
}
}
return@HandleHomeTransition null
@ -80,29 +111,42 @@ fun GridItem(modifier: Modifier = Modifier, item: SavableSearchable, showLabels:
}
val hapticFeedback = LocalHapticFeedback.current
ShapedLauncherIcon(
modifier = Modifier
.onGloballyPositioned {
bounds = it.boundsInWindow()
val iconShape = LocalIconShape.current
Box(
modifier = if (highlight) {
Modifier
.background(
MaterialTheme.colorScheme.outlineVariant,
iconShape
)
} else Modifier,
) {
ShapedLauncherIcon(
modifier = Modifier
.padding(4.dp)
.onGloballyPositioned {
bounds = it.boundsInWindow()
},
size = LocalGridSettings.current.iconSize.dp,
badge = { badge },
icon = { icon },
onClick = {
if (!launchOnPress || !viewModel.launch(context, bounds)) {
showPopup = true
}
},
size = LocalGridSettings.current.iconSize.dp,
badge = { badge },
icon = { icon },
onClick = {
if (!launchOnPress || !viewModel.launch(context, bounds)) {
onLongClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
showPopup = true
}
},
onLongClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
showPopup = true
}
)
)
}
if (showLabels) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
.padding(vertical = 4.dp),
text = item.labelOverride ?: item.label,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodySmall,
@ -110,6 +154,7 @@ fun GridItem(modifier: Modifier = Modifier, item: SavableSearchable, showLabels:
overflow = TextOverflow.Ellipsis
)
}
if (showPopup) {
ItemPopup(origin = bounds, searchable = item, onDismissRequest = { showPopup = false })
}
@ -158,6 +203,7 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit
x = ((1 - animationProgress) * origin.left).toDp() - 16.dp * (1 - animationProgress),
)
.wrapContentSize()
.padding(4.dp)
) {
when (searchable) {
is LauncherApp -> {
@ -171,6 +217,7 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit
}
)
}
is Website -> {
WebsiteItemGridPopup(
website = searchable,
@ -182,6 +229,7 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit
}
)
}
is Wikipedia -> {
WikipediaItemGridPopup(
wikipedia = searchable,
@ -193,6 +241,7 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit
}
)
}
is Contact -> {
ContactItemGridPopup(
contact = searchable,
@ -204,6 +253,7 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit
}
)
}
is File -> {
FileItemGridPopup(
file = searchable,
@ -215,6 +265,7 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit
}
)
}
is CalendarEvent -> {
CalendarItemGridPopup(
calendar = searchable,
@ -226,6 +277,7 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit
}
)
}
is AppShortcut -> {
ShortcutItemGridPopup(
shortcut = searchable,

View File

@ -2,6 +2,7 @@ package de.mm20.launcher2.ui.launcher.search.common.grid
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.*
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@ -16,7 +17,8 @@ fun SearchResultGrid(
modifier: Modifier = Modifier,
showLabels: Boolean = LocalGridSettings.current.showLabels,
columns: Int = LocalGridSettings.current.columnCount,
reverse: Boolean = false
reverse: Boolean = false,
highlightedItem: SavableSearchable? = null
) {
Column(
modifier = modifier
@ -33,9 +35,10 @@ fun SearchResultGrid(
GridItem(
modifier = Modifier
.weight(1f)
.padding(4.dp, 8.dp),
.padding(4.dp),
item = item,
showLabels = showLabels
showLabels = showLabels,
highlight = item.key == highlightedItem?.key
)
} else {
Spacer(modifier = Modifier.weight(1f))

View File

@ -17,7 +17,11 @@ import de.mm20.launcher2.ui.launcher.search.files.FileItem
import de.mm20.launcher2.ui.launcher.search.shortcut.AppShortcutItem
@Composable
fun ListItem(modifier: Modifier = Modifier, item: SavableSearchable) {
fun ListItem(
modifier: Modifier = Modifier,
item: SavableSearchable,
highlight: Boolean = false
) {
var showDetails by remember { mutableStateOf(false) }
val context = LocalContext.current
@ -29,6 +33,7 @@ fun ListItem(modifier: Modifier = Modifier, item: SavableSearchable) {
.onGloballyPositioned {
bounds = it.boundsInWindow()
},
highlight = highlight,
raised = showDetails
) {
when (item) {

View File

@ -15,7 +15,8 @@ import de.mm20.launcher2.ui.layout.BottomReversed
fun SearchResultList(
items: List<SavableSearchable>,
modifier: Modifier = Modifier,
reverse: Boolean = false
reverse: Boolean = false,
highlightedItem: SavableSearchable? = null
) {
Column(
modifier = modifier,
@ -27,7 +28,8 @@ fun SearchResultList(
modifier = Modifier
.fillMaxWidth()
.padding(4.dp),
item = item
item = item,
highlight = item.key == highlightedItem?.key
)
}
}

View File

@ -1,68 +0,0 @@
package de.mm20.launcher2.ui.launcher.search.contacts
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
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.ui.R
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList
@Composable
fun ColumnScope.ContactResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val context = LocalContext.current
val contacts by viewModel.contactResults.observeAsState(emptyList())
val isSearchEmpty by viewModel.isSearchEmpty.observeAsState(true)
val missingPermission by viewModel.missingContactsPermission.collectAsState(false)
AnimatedVisibility(contacts.isNotEmpty() || (!isSearchEmpty && missingPermission)) {
LauncherCard(
modifier = Modifier
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
.fillMaxWidth()
) {
Column {
AnimatedVisibility(!isSearchEmpty && missingPermission) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_contact_search),
onClick = { viewModel.requestContactsPermission(context as AppCompatActivity) },
modifier = Modifier.padding(16.dp),
secondaryAction = {
OutlinedButton(onClick = {
viewModel.disableContactsSearch()
}) {
Text(
stringResource(R.string.turn_off),
)
}
}
)
}
SearchResultList(
items = contacts,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
reverse = reverse
)
}
}
}
}

View File

@ -1,67 +0,0 @@
package de.mm20.launcher2.ui.launcher.search.files
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
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.ui.R
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList
@Composable
fun ColumnScope.FileResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val files by viewModel.fileResults.observeAsState(emptyList())
val context = LocalContext.current
val isSearchEmpty by viewModel.isSearchEmpty.observeAsState(true)
val missingPermission by viewModel.missingFilesPermission.collectAsState(false)
AnimatedVisibility(files.isNotEmpty() || (!isSearchEmpty && missingPermission)) {
LauncherCard(
modifier = Modifier
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
.fillMaxWidth()
) {
Column {
AnimatedVisibility(!isSearchEmpty && missingPermission) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_files_search),
onClick = { viewModel.requestFilesPermission(context as AppCompatActivity) },
modifier = Modifier.padding(16.dp),
secondaryAction = {
OutlinedButton(onClick = {
viewModel.disableFilesSearch()
}) {
Text(
stringResource(R.string.turn_off),
)
}
}
)
}
SearchResultList(
items = files,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
reverse = reverse
)}
}
}
}

View File

@ -4,6 +4,7 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.text.KeyboardActionScope
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.VisibilityOff
import androidx.compose.material3.FilledIconButton
@ -13,9 +14,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.LocalFocusManager
@ -24,7 +23,6 @@ import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.searchactions.actions.SearchAction
import de.mm20.launcher2.ui.component.SearchBar
import de.mm20.launcher2.ui.component.SearchBarLevel
import de.mm20.launcher2.ui.launcher.sheets.HiddenItemsSheet
import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
@ -38,9 +36,11 @@ fun LauncherSearchBar(
focused: Boolean,
onFocusChange: (Boolean) -> Unit,
actions: List<SearchAction>,
highlightedAction: SearchAction?,
showHiddenItemsButton: Boolean = false,
reverse: Boolean = false,
darkColors: Boolean = false,
onKeyboardActionGo: (KeyboardActionScope.() -> Unit)? = null
) {
val focusManager = LocalFocusManager.current
val focusRequester = remember { FocusRequester() }
@ -51,7 +51,6 @@ fun LauncherSearchBar(
val hiddenItems by searchVM.hiddenResults.observeAsState(emptyList())
LaunchedEffect(focused) {
if (focused) focusRequester.requestFocus()
else focusManager.clearFocus()
@ -80,10 +79,11 @@ fun LauncherSearchBar(
SearchBarMenu(searchBarValue = _value, onSearchBarValueChange = onValueChange)
},
actions = {
SearchBarActions(actions = actions, reverse = reverse)
SearchBarActions(actions = actions, reverse = reverse, highlightedAction = highlightedAction)
},
focusRequester = focusRequester,
onFocus = { onFocusChange(true) },
onUnfocus = { onFocusChange(false) },
onKeyboardActionGo = onKeyboardActionGo
)
}

View File

@ -11,8 +11,10 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material3.AssistChip
import androidx.compose.material3.AssistChipDefaults
import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -28,6 +30,7 @@ import de.mm20.launcher2.ui.settings.SettingsActivity
fun ColumnScope.SearchBarActions(
modifier: Modifier = Modifier,
actions: List<SearchAction>,
highlightedAction: SearchAction?,
reverse: Boolean = false,
) {
val context = LocalContext.current
@ -42,6 +45,16 @@ fun ColumnScope.SearchBarActions(
items(actions) {
AssistChip(
modifier = Modifier.padding(4.dp),
colors = if (it == highlightedAction) {
AssistChipDefaults.assistChipColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer,
)
} else AssistChipDefaults.assistChipColors(),
border = if (it == highlightedAction) {
AssistChipDefaults.assistChipBorder(
borderColor = MaterialTheme.colorScheme.secondary,
)
} else AssistChipDefaults.assistChipBorder(),
onClick = {
it.start(context)
},
@ -51,24 +64,6 @@ fun ColumnScope.SearchBarActions(
action = it
)
}
/*leadingIcon = {
val icon = it.icon
if (icon == null) {
Icon(
imageVector = Icons.Rounded.Search,
contentDescription = null,
tint = if (it.color == 0) MaterialTheme.colorScheme.primary else Color(
it.color
)
)
} else {
AsyncImage(
modifier = Modifier.size(24.dp),
model = File(icon),
contentDescription = null
)
}
}*/
)
}
item {

View File

@ -453,7 +453,8 @@ fun SearchBarStylePreference(
level = level,
style = styles[it],
value = previewSearchValue,
onValueChange = {})
onValueChange = {}
)
}
HorizontalPagerIndicator(pagerState = pagerState)
}

View File

@ -181,14 +181,24 @@ fun SearchSettingsScreen() {
}
item {
val autoFocus by viewModel.autoFocus.observeAsState()
val launchOnEnter by viewModel.launchOnEnter.observeAsState()
PreferenceCategory {
SwitchPreference(
title = stringResource(R.string.preference_search_bar_auto_focus),
summary = stringResource(R.string.preference_search_bar_auto_focus_summary),
value = autoFocus == true,
onValueChanged = {
viewModel.setAutoFocus(it)
})
viewModel.setAutoFocus(it)
}
)
SwitchPreference(
title = stringResource(R.string.preference_search_bar_launch_on_enter),
summary = stringResource(R.string.preference_search_bar_launch_on_enter_summary),
value = launchOnEnter == true,
onValueChanged = {
viewModel.setLaunchOnEnter(it)
}
)
Preference(
title = stringResource(R.string.preference_hidden_items),
summary = stringResource(R.string.preference_hidden_items_summary),

View File

@ -151,6 +151,20 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
}
}
val launchOnEnter = dataStore.data.map { it.searchBar.launchOnEnter }.asLiveData()
fun setLaunchOnEnter(launchOnEnter: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setSearchBar(
it.searchBar.toBuilder()
.setLaunchOnEnter(launchOnEnter)
)
.build()
}
}
}
val hasAppShortcutPermission = permissionsManager.hasPermission(PermissionGroup.AppShortcuts).asLiveData()
val appShortcuts = dataStore.data.map { it.appShortcutSearch.enabled }.asLiveData()

View File

@ -347,6 +347,8 @@
<string name="preference_search_bar_style_summary">Erscheinungsbild der Suchleiste anpassen</string>
<string name="preference_search_bar_auto_focus">Tastatur öffnen</string>
<string name="preference_search_bar_auto_focus_summary">Bildschirmtastatur beim Öffnen der Suche automatisch einblenden</string>
<string name="preference_search_bar_launch_on_enter">Eingabe zum Starten</string>
<string name="preference_search_bar_launch_on_enter_summary">Hervorgehobenes Suchergebnis oder Schnellaktion beim Berühren der Start-Taste aufrufen</string>
<string name="preference_wikipedia_customurl">Wikipedia-URL</string>
<string name="music_widget_default_title">%1$s spielt Medien</string>
<string name="music_widget_no_data">Bisher wurden keine Medien abgespielt</string>
@ -572,4 +574,4 @@
<string name="preference_layout_fixed_rotation">Feste Bildschirmausrichtung</string>
<string name="preference_layout_fixed_rotation_summary">Porträtmodus erzwingen</string>
<string name="icon_pack_dynamic_colors">Dynamische Farben</string>
</resources>
</resources>

View File

@ -631,6 +631,8 @@
<string name="preference_search_bar_color">Color</string>
<string name="preference_search_bar_auto_focus">Open keyboard</string>
<string name="preference_search_bar_auto_focus_summary">Automatically show the keyboard when opening the search</string>
<string name="preference_search_bar_launch_on_enter">Launch on enter</string>
<string name="preference_search_bar_launch_on_enter_summary">Launch highlighted match or quick-action upon tapping go</string>
<string name="preference_hidden_items">Hidden search results</string>
<string name="preference_hidden_items_summary">Manage hidden apps and search results</string>
<string name="preference_screen_tags">Tags</string>
@ -756,4 +758,4 @@
<string name="gesture_failed_message">You have performed a \"%1$s\" gesture. This gesture is currently set to trigger a \"%2$s\" action. However, the action could not be performed for the following reason:</string>
<string name="missing_permission_accessibility_gesture_failed">The launcher\'s accessibility service needs to be enabled to perform this action.</string>
<string name="missing_permission_accessibility_gesture_settings">This action requires the launcher\'s accessibility service to be enabled.</string>
</resources>
</resources>

View File

@ -222,6 +222,7 @@ message Settings {
Dark = 2;
}
SearchBarColors color = 3;
bool launch_on_enter = 4;
}
SearchBarSettings search_bar = 20;