Add websearch quick action

Close #373, #443
This commit is contained in:
MM20 2023-07-21 14:04:08 +02:00
parent afd407dfc2
commit f4b217b30c
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
10 changed files with 146 additions and 97 deletions

View File

@ -85,7 +85,7 @@ import de.mm20.launcher2.searchactions.actions.SearchActionIcon
import de.mm20.launcher2.searchactions.builders.AppSearchActionBuilder import de.mm20.launcher2.searchactions.builders.AppSearchActionBuilder
import de.mm20.launcher2.searchactions.builders.CustomIntentActionBuilder import de.mm20.launcher2.searchactions.builders.CustomIntentActionBuilder
import de.mm20.launcher2.searchactions.builders.CustomizableSearchActionBuilder import de.mm20.launcher2.searchactions.builders.CustomizableSearchActionBuilder
import de.mm20.launcher2.searchactions.builders.WebsearchActionBuilder import de.mm20.launcher2.searchactions.builders.CustomWebsearchActionBuilder
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.ExperimentalBadge import de.mm20.launcher2.ui.component.ExperimentalBadge
@ -406,7 +406,7 @@ fun CustomizeWebSearch(viewModel: EditSearchActionSheetVM, paddingValues: Paddin
.padding(paddingValues) .padding(paddingValues)
) { ) {
if (searchAction != null && searchAction is WebsearchActionBuilder) { if (searchAction != null && searchAction is CustomWebsearchActionBuilder) {
Row( Row(
modifier = Modifier.padding(bottom = 16.dp), modifier = Modifier.padding(bottom = 16.dp),
verticalAlignment = Alignment.Bottom verticalAlignment = Alignment.Bottom
@ -436,7 +436,7 @@ fun CustomizeWebSearch(viewModel: EditSearchActionSheetVM, paddingValues: Paddin
modifier = Modifier modifier = Modifier
.fillMaxWidth(), .fillMaxWidth(),
singleLine = true, singleLine = true,
value = (searchAction as WebsearchActionBuilder).urlTemplate, value = (searchAction as CustomWebsearchActionBuilder).urlTemplate,
onValueChange = { viewModel.setUrlTemplate(it) }, onValueChange = { viewModel.setUrlTemplate(it) },
label = { Text(stringResource(R.string.search_action_websearch_url)) }, label = { Text(stringResource(R.string.search_action_websearch_url)) },
supportingText = { supportingText = {
@ -507,11 +507,11 @@ fun CustomizeWebSearch(viewModel: EditSearchActionSheetVM, paddingValues: Paddin
ListPreference( ListPreference(
title = stringResource(R.string.websearch_dialog_query_encoding), title = stringResource(R.string.websearch_dialog_query_encoding),
items = listOf( items = listOf(
stringResource(id = R.string.websearch_dialog_query_encoding_url) to WebsearchActionBuilder.QueryEncoding.UrlEncode, stringResource(id = R.string.websearch_dialog_query_encoding_url) to CustomWebsearchActionBuilder.QueryEncoding.UrlEncode,
stringResource(id = R.string.websearch_dialog_query_encoding_form) to WebsearchActionBuilder.QueryEncoding.FormData, stringResource(id = R.string.websearch_dialog_query_encoding_form) to CustomWebsearchActionBuilder.QueryEncoding.FormData,
stringResource(id = R.string.websearch_dialog_query_encoding_none) to WebsearchActionBuilder.QueryEncoding.None, stringResource(id = R.string.websearch_dialog_query_encoding_none) to CustomWebsearchActionBuilder.QueryEncoding.None,
), ),
value = (searchAction as WebsearchActionBuilder).encoding, value = (searchAction as CustomWebsearchActionBuilder).encoding,
onValueChanged = { onValueChanged = {
viewModel.setQueryEncoding(it) viewModel.setQueryEncoding(it)
}, },

View File

@ -17,7 +17,7 @@ import de.mm20.launcher2.searchactions.actions.SearchActionIcon
import de.mm20.launcher2.searchactions.builders.AppSearchActionBuilder import de.mm20.launcher2.searchactions.builders.AppSearchActionBuilder
import de.mm20.launcher2.searchactions.builders.CustomIntentActionBuilder import de.mm20.launcher2.searchactions.builders.CustomIntentActionBuilder
import de.mm20.launcher2.searchactions.builders.CustomizableSearchActionBuilder import de.mm20.launcher2.searchactions.builders.CustomizableSearchActionBuilder
import de.mm20.launcher2.searchactions.builders.WebsearchActionBuilder import de.mm20.launcher2.searchactions.builders.CustomWebsearchActionBuilder
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -41,7 +41,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
initialCustomIcon = searchAction?.customIcon initialCustomIcon = searchAction?.customIcon
currentPage.value = when (searchAction) { currentPage.value = when (searchAction) {
is AppSearchActionBuilder -> EditSearchActionPage.CustomizeAppSearch is AppSearchActionBuilder -> EditSearchActionPage.CustomizeAppSearch
is WebsearchActionBuilder -> EditSearchActionPage.CustomizeWebSearch is CustomWebsearchActionBuilder -> EditSearchActionPage.CustomizeWebSearch
is CustomIntentActionBuilder -> EditSearchActionPage.CustomizeCustomIntent is CustomIntentActionBuilder -> EditSearchActionPage.CustomizeCustomIntent
else -> EditSearchActionPage.SelectType else -> EditSearchActionPage.SelectType
} }
@ -100,7 +100,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
val newAction = when (action) { val newAction = when (action) {
is CustomIntentActionBuilder -> action.copy(label = label) is CustomIntentActionBuilder -> action.copy(label = label)
is AppSearchActionBuilder -> action.copy(label = label) is AppSearchActionBuilder -> action.copy(label = label)
is WebsearchActionBuilder -> action.copy(label = label) is CustomWebsearchActionBuilder -> action.copy(label = label)
} }
searchAction.value = newAction searchAction.value = newAction
@ -120,7 +120,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
it.baseIntent.setComponent(componentName) it.baseIntent.setComponent(componentName)
} }
is WebsearchActionBuilder -> action is CustomWebsearchActionBuilder -> action
} }
searchAction.value = newAction searchAction.value = newAction
@ -160,7 +160,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
} }
fun skipWebsearchImport() { fun skipWebsearchImport() {
searchAction.value = WebsearchActionBuilder( searchAction.value = CustomWebsearchActionBuilder(
urlTemplate = "", urlTemplate = "",
iconColor = 0, iconColor = 0,
icon = SearchActionIcon.Search, icon = SearchActionIcon.Search,
@ -171,7 +171,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
fun setUrlTemplate(template: String) { fun setUrlTemplate(template: String) {
val action = searchAction.value ?: return val action = searchAction.value ?: return
if (action is WebsearchActionBuilder) { if (action is CustomWebsearchActionBuilder) {
searchAction.value = action.copy( searchAction.value = action.copy(
urlTemplate = template urlTemplate = template
) )
@ -181,12 +181,12 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
private val invalidWebsearchUrl = mutableStateOf<String?>(null) private val invalidWebsearchUrl = mutableStateOf<String?>(null)
val websearchInvalidUrlError = val websearchInvalidUrlError =
derivedStateOf { invalidWebsearchUrl.value == (searchAction.value as? WebsearchActionBuilder)?.urlTemplate } derivedStateOf { invalidWebsearchUrl.value == (searchAction.value as? CustomWebsearchActionBuilder)?.urlTemplate }
val customIntentKeyError = mutableStateOf(false) val customIntentKeyError = mutableStateOf(false)
fun validate(): Boolean { fun validate(): Boolean {
val action = searchAction.value ?: return false val action = searchAction.value ?: return false
if (action is WebsearchActionBuilder) { if (action is CustomWebsearchActionBuilder) {
val valid = action.urlTemplate.contains("\${1}") val valid = action.urlTemplate.contains("\${1}")
invalidWebsearchUrl.value = if (valid) null else action.urlTemplate invalidWebsearchUrl.value = if (valid) null else action.urlTemplate
return valid return valid
@ -218,7 +218,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
deleteCustomIcon(action.customIcon) deleteCustomIcon(action.customIcon)
} }
searchAction.value = when (action) { searchAction.value = when (action) {
is WebsearchActionBuilder -> action.copy(icon = icon, customIcon = null, iconColor = 0) is CustomWebsearchActionBuilder -> action.copy(icon = icon, customIcon = null, iconColor = 0)
is CustomIntentActionBuilder -> action.copy( is CustomIntentActionBuilder -> action.copy(
icon = icon, icon = icon,
customIcon = null, customIcon = null,
@ -235,7 +235,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
deleteCustomIcon(action.customIcon) deleteCustomIcon(action.customIcon)
} }
searchAction.value = when (action) { searchAction.value = when (action) {
is WebsearchActionBuilder -> action.copy( is CustomWebsearchActionBuilder -> action.copy(
customIcon = iconPath, customIcon = iconPath,
iconColor = 1, iconColor = 1,
icon = SearchActionIcon.Custom icon = SearchActionIcon.Custom
@ -269,7 +269,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
fun applyIcon() { fun applyIcon() {
currentPage.value = when (searchAction.value) { currentPage.value = when (searchAction.value) {
is AppSearchActionBuilder -> EditSearchActionPage.CustomizeAppSearch is AppSearchActionBuilder -> EditSearchActionPage.CustomizeAppSearch
is WebsearchActionBuilder -> EditSearchActionPage.CustomizeWebSearch is CustomWebsearchActionBuilder -> EditSearchActionPage.CustomizeWebSearch
is CustomIntentActionBuilder -> EditSearchActionPage.CustomizeCustomIntent is CustomIntentActionBuilder -> EditSearchActionPage.CustomizeCustomIntent
null -> EditSearchActionPage.SelectType null -> EditSearchActionPage.SelectType
} }
@ -278,7 +278,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
fun setIconColor(color: Int) { fun setIconColor(color: Int) {
val action = searchAction.value ?: return val action = searchAction.value ?: return
searchAction.value = when (action) { searchAction.value = when (action) {
is WebsearchActionBuilder -> action.copy(iconColor = color) is CustomWebsearchActionBuilder -> action.copy(iconColor = color)
is CustomIntentActionBuilder -> action.copy(iconColor = color) is CustomIntentActionBuilder -> action.copy(iconColor = color)
is AppSearchActionBuilder -> action.copy(iconColor = color) is AppSearchActionBuilder -> action.copy(iconColor = color)
} }
@ -495,10 +495,10 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
} }
} }
fun setQueryEncoding(encoding: WebsearchActionBuilder.QueryEncoding) { fun setQueryEncoding(encoding: CustomWebsearchActionBuilder.QueryEncoding) {
val action = searchAction.value ?: return val action = searchAction.value ?: return
searchAction.value = when (action) { searchAction.value = when (action) {
is WebsearchActionBuilder -> action.copy( is CustomWebsearchActionBuilder -> action.copy(
encoding = encoding encoding = encoding
) )

View File

@ -727,6 +727,7 @@
<string name="search_action_email">Email</string> <string name="search_action_email">Email</string>
<string name="search_action_alarm">Set alarm</string> <string name="search_action_alarm">Set alarm</string>
<string name="search_action_timer">Start timer</string> <string name="search_action_timer">Start timer</string>
<string name="search_action_websearch">Web search</string>
<string name="search_action_contact">Add to contacts</string> <string name="search_action_contact">Add to contacts</string>
<string name="search_action_open_url">View website</string> <string name="search_action_open_url">View website</string>
<string name="search_action_event">Schedule event</string> <string name="search_action_event">Schedule event</string>

View File

@ -1,21 +1,21 @@
package de.mm20.launcher2.searchactions package de.mm20.launcher2.searchactions
import de.mm20.launcher2.searchactions.builders.WebsearchActionBuilder import de.mm20.launcher2.searchactions.builders.CustomWebsearchActionBuilder
fun knownWebsearchByHostname(hostname: String): WebsearchActionBuilder? { fun knownWebsearchByHostname(hostname: String): CustomWebsearchActionBuilder? {
// List of popular web search engines that do not implement the OpenSearch standard // List of popular web search engines that do not implement the OpenSearch standard
return when(hostname) { return when(hostname) {
"google.com" -> WebsearchActionBuilder(label = "Google", urlTemplate = "https://google.com/search?q=\${1}") "google.com" -> CustomWebsearchActionBuilder(label = "Google", urlTemplate = "https://google.com/search?q=\${1}")
"bing.com" -> WebsearchActionBuilder(label = "Google", urlTemplate = "https://bing.com/search?q=\${1}") "bing.com" -> CustomWebsearchActionBuilder(label = "Google", urlTemplate = "https://bing.com/search?q=\${1}")
"amazon.com" -> WebsearchActionBuilder(label = "Amazon", urlTemplate = "https://www.amazon.com/s?k=\${1}") "amazon.com" -> CustomWebsearchActionBuilder(label = "Amazon", urlTemplate = "https://www.amazon.com/s?k=\${1}")
"amazon.de" -> WebsearchActionBuilder(label = "Amazon DE", urlTemplate = "https://www.amazon.de/s?k=\${1}") "amazon.de" -> CustomWebsearchActionBuilder(label = "Amazon DE", urlTemplate = "https://www.amazon.de/s?k=\${1}")
"amazon.co.uk" -> WebsearchActionBuilder(label = "Amazon UK", urlTemplate = "https://www.amazon.co.uk/s?k=\${1}") "amazon.co.uk" -> CustomWebsearchActionBuilder(label = "Amazon UK", urlTemplate = "https://www.amazon.co.uk/s?k=\${1}")
"amazon.fr" -> WebsearchActionBuilder(label = "Amazon FR", urlTemplate = "https://www.amazon.fr/s?k=\${1}") "amazon.fr" -> CustomWebsearchActionBuilder(label = "Amazon FR", urlTemplate = "https://www.amazon.fr/s?k=\${1}")
"amazon.co.jp" -> WebsearchActionBuilder(label = "Amazon JP", urlTemplate = "https://www.amazon.co.jp/s?k=\${1}") "amazon.co.jp" -> CustomWebsearchActionBuilder(label = "Amazon JP", urlTemplate = "https://www.amazon.co.jp/s?k=\${1}")
"amazon.ca" -> WebsearchActionBuilder(label = "Amazon CA", urlTemplate = "https://www.amazon.ca/s?k=\${1}") "amazon.ca" -> CustomWebsearchActionBuilder(label = "Amazon CA", urlTemplate = "https://www.amazon.ca/s?k=\${1}")
"amazon.cn" -> WebsearchActionBuilder(label = "Amazon CN", urlTemplate = "https://www.amazon.cn/s?k=\${1}") "amazon.cn" -> CustomWebsearchActionBuilder(label = "Amazon CN", urlTemplate = "https://www.amazon.cn/s?k=\${1}")
"duckduckgo.com" -> WebsearchActionBuilder(label = "DuckDuckGo", urlTemplate = "https://duckduckgo.com/?q=\${1}") "duckduckgo.com" -> CustomWebsearchActionBuilder(label = "DuckDuckGo", urlTemplate = "https://duckduckgo.com/?q=\${1}")
"yahoo.com" -> WebsearchActionBuilder(label = "DuckDuckGo", urlTemplate = "https://search.yahoo.com/search?p=\${1}") "yahoo.com" -> CustomWebsearchActionBuilder(label = "Yahoo", urlTemplate = "https://search.yahoo.com/search?p=\${1}")
else -> null else -> null
} }
} }

View File

@ -14,6 +14,7 @@ import de.mm20.launcher2.searchactions.builders.ScheduleEventActionBuilder
import de.mm20.launcher2.searchactions.builders.SearchActionBuilder import de.mm20.launcher2.searchactions.builders.SearchActionBuilder
import de.mm20.launcher2.searchactions.builders.SetAlarmActionBuilder import de.mm20.launcher2.searchactions.builders.SetAlarmActionBuilder
import de.mm20.launcher2.searchactions.builders.TimerActionBuilder import de.mm20.launcher2.searchactions.builders.TimerActionBuilder
import de.mm20.launcher2.searchactions.builders.WebsearchActionBuilder
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@ -57,6 +58,7 @@ internal class SearchActionRepositoryImpl(
SetAlarmActionBuilder(context), SetAlarmActionBuilder(context),
TimerActionBuilder(context), TimerActionBuilder(context),
OpenUrlActionBuilder(context), OpenUrlActionBuilder(context),
WebsearchActionBuilder(context),
) )
return allActions return allActions

View File

@ -15,7 +15,7 @@ import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.searchactions.actions.SearchAction import de.mm20.launcher2.searchactions.actions.SearchAction
import de.mm20.launcher2.searchactions.actions.SearchActionIcon import de.mm20.launcher2.searchactions.actions.SearchActionIcon
import de.mm20.launcher2.searchactions.builders.SearchActionBuilder import de.mm20.launcher2.searchactions.builders.SearchActionBuilder
import de.mm20.launcher2.searchactions.builders.WebsearchActionBuilder import de.mm20.launcher2.searchactions.builders.CustomWebsearchActionBuilder
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@ -43,7 +43,7 @@ interface SearchActionService {
fun saveSearchActionBuilders(builders: List<SearchActionBuilder>) fun saveSearchActionBuilders(builders: List<SearchActionBuilder>)
suspend fun importWebsearch(url: String, iconSize: Int): WebsearchActionBuilder? suspend fun importWebsearch(url: String, iconSize: Int): CustomWebsearchActionBuilder?
suspend fun getSearchActivities(): List<ComponentName> suspend fun getSearchActivities(): List<ComponentName>
@ -88,7 +88,7 @@ internal class SearchActionServiceImpl(
repository.saveSearchActionBuilders(builders) repository.saveSearchActionBuilders(builders)
} }
override suspend fun importWebsearch(url: String, iconSize: Int): WebsearchActionBuilder? = override suspend fun importWebsearch(url: String, iconSize: Int): CustomWebsearchActionBuilder? =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
val u = if (url.startsWith("http://") || url.startsWith("https://")) { val u = if (url.startsWith("http://") || url.startsWith("https://")) {
@ -98,7 +98,7 @@ internal class SearchActionServiceImpl(
} }
if (u.contains("${1}")) { if (u.contains("${1}")) {
return@withContext WebsearchActionBuilder( return@withContext CustomWebsearchActionBuilder(
urlTemplate = u, urlTemplate = u,
label = "", label = "",
iconColor = 0, iconColor = 0,
@ -136,7 +136,7 @@ internal class SearchActionServiceImpl(
private suspend fun importOpenSearch( private suspend fun importOpenSearch(
openSearchHref: String, openSearchHref: String,
iconSize: Int iconSize: Int
): WebsearchActionBuilder? { ): CustomWebsearchActionBuilder? {
try { try {
val httpClient = OkHttpClient() val httpClient = OkHttpClient()
val request = Request.Builder() val request = Request.Builder()
@ -207,7 +207,7 @@ internal class SearchActionServiceImpl(
createIcon(uri, iconSize) createIcon(uri, iconSize)
} }
return WebsearchActionBuilder( return CustomWebsearchActionBuilder(
label = label ?: "", label = label ?: "",
icon = if (localIconUrl == null) SearchActionIcon.Search else SearchActionIcon.Custom, icon = if (localIconUrl == null) SearchActionIcon.Search else SearchActionIcon.Custom,
customIcon = localIconUrl, customIcon = localIconUrl,

View File

@ -0,0 +1,22 @@
package de.mm20.launcher2.searchactions.actions
import android.app.SearchManager
import android.content.Context
import android.content.Intent
import de.mm20.launcher2.ktx.tryStartActivity
data class WebsearchAction(
override val label: String,
val query: String,
): SearchAction {
override val icon: SearchActionIcon = SearchActionIcon.WebSearch
override val iconColor: Int = 0
override val customIcon: String? = null
override fun start(context: Context) {
val intent = Intent(Intent.ACTION_WEB_SEARCH).apply {
putExtra(SearchManager.QUERY, query)
}
context.tryStartActivity(intent)
}
}

View File

@ -0,0 +1,66 @@
package de.mm20.launcher2.searchactions.builders
import android.content.Context
import android.net.Uri
import de.mm20.launcher2.searchactions.TextClassificationResult
import de.mm20.launcher2.searchactions.actions.OpenUrlAction
import de.mm20.launcher2.searchactions.actions.SearchAction
import de.mm20.launcher2.searchactions.actions.SearchActionIcon
import java.net.URLEncoder
data class CustomWebsearchActionBuilder(
override val label: String,
val urlTemplate: String,
override val icon: SearchActionIcon = SearchActionIcon.Search,
override val iconColor: Int = 0,
override val customIcon: String? = null,
val encoding: QueryEncoding = QueryEncoding.UrlEncode,
) : CustomizableSearchActionBuilder {
override val key: String
get() = "web://$urlTemplate"
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction {
val url = urlTemplate.replace("\${1}", encodeQuery(classifiedQuery.text, encoding))
return OpenUrlAction(
label = label,
url = url,
icon = icon,
customIcon = customIcon,
iconColor = iconColor,
)
}
private fun encodeQuery(query: String, encoding: QueryEncoding): String {
return when (encoding) {
QueryEncoding.UrlEncode -> Uri.encode(query)
QueryEncoding.FormData -> URLEncoder.encode(query, "UTF-8")
QueryEncoding.None -> query
}
}
enum class QueryEncoding {
UrlEncode,
FormData,
None;
fun toInt(): Int {
return when (this) {
UrlEncode -> 0
FormData -> 1
None -> 2
}
}
companion object {
fun fromInt(value: Int?): QueryEncoding {
return when (value) {
1 -> FormData
2 -> None
else -> UrlEncode
}
}
}
}
}

View File

@ -1,9 +1,7 @@
package de.mm20.launcher2.searchactions.builders package de.mm20.launcher2.searchactions.builders
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.media.metrics.Event
import de.mm20.launcher2.database.entities.SearchActionEntity import de.mm20.launcher2.database.entities.SearchActionEntity
import de.mm20.launcher2.ktx.jsonObjectOf import de.mm20.launcher2.ktx.jsonObjectOf
import de.mm20.launcher2.searchactions.TextClassificationResult import de.mm20.launcher2.searchactions.TextClassificationResult
@ -34,13 +32,13 @@ interface SearchActionBuilder {
} }
when (entity.type) { when (entity.type) {
"url" -> { "url" -> {
return WebsearchActionBuilder( return CustomWebsearchActionBuilder(
label = entity.label ?: "", label = entity.label ?: "",
urlTemplate = entity.data ?: return null, urlTemplate = entity.data ?: return null,
iconColor = entity.color ?: 0, iconColor = entity.color ?: 0,
icon = SearchActionIcon.fromInt(entity.icon), icon = SearchActionIcon.fromInt(entity.icon),
customIcon = entity.customIcon, customIcon = entity.customIcon,
encoding = WebsearchActionBuilder.QueryEncoding.fromInt(options?.optInt("encoding")) encoding = CustomWebsearchActionBuilder.QueryEncoding.fromInt(options?.optInt("encoding"))
) )
} }
"app" -> { "app" -> {
@ -70,13 +68,14 @@ interface SearchActionBuilder {
"timer" -> return TimerActionBuilder(context) "timer" -> return TimerActionBuilder(context)
"calendar" -> return ScheduleEventActionBuilder(context) "calendar" -> return ScheduleEventActionBuilder(context)
"website" -> return OpenUrlActionBuilder(context) "website" -> return OpenUrlActionBuilder(context)
"websearch" -> return WebsearchActionBuilder(context)
else -> return null else -> return null
} }
} }
internal fun toDatabaseEntity(builder: SearchActionBuilder, position: Int): SearchActionEntity { internal fun toDatabaseEntity(builder: SearchActionBuilder, position: Int): SearchActionEntity {
return when(builder) { return when(builder) {
is WebsearchActionBuilder -> SearchActionEntity( is CustomWebsearchActionBuilder -> SearchActionEntity(
position = position, position = position,
type = "url", type = "url",
label = builder.label, label = builder.label,

View File

@ -1,66 +1,25 @@
package de.mm20.launcher2.searchactions.builders package de.mm20.launcher2.searchactions.builders
import android.content.Context import android.content.Context
import android.net.Uri import de.mm20.launcher2.searchactions.R
import de.mm20.launcher2.searchactions.TextClassificationResult import de.mm20.launcher2.searchactions.TextClassificationResult
import de.mm20.launcher2.searchactions.actions.OpenUrlAction
import de.mm20.launcher2.searchactions.actions.SearchAction import de.mm20.launcher2.searchactions.actions.SearchAction
import de.mm20.launcher2.searchactions.actions.SearchActionIcon import de.mm20.launcher2.searchactions.actions.SearchActionIcon
import java.net.URLEncoder import de.mm20.launcher2.searchactions.actions.WebsearchAction
data class WebsearchActionBuilder( class WebsearchActionBuilder(
override val label: String, override val label: String,
val urlTemplate: String, ) : SearchActionBuilder {
override val icon: SearchActionIcon = SearchActionIcon.Search,
override val iconColor: Int = 0,
override val customIcon: String? = null,
val encoding: QueryEncoding = QueryEncoding.UrlEncode,
) : CustomizableSearchActionBuilder {
constructor(context: Context) : this(context.getString(R.string.search_action_websearch))
override val icon: SearchActionIcon = SearchActionIcon.WebSearch
override val key: String override val key: String
get() = "web://$urlTemplate" get() = "websearch"
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction { override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
val url = urlTemplate.replace("\${1}", encodeQuery(classifiedQuery.text, encoding)) return WebsearchAction(
return OpenUrlAction( context.getString(R.string.search_action_websearch), classifiedQuery.text
label = label,
url = url,
icon = icon,
customIcon = customIcon,
iconColor = iconColor,
) )
} }
private fun encodeQuery(query: String, encoding: QueryEncoding): String {
return when (encoding) {
QueryEncoding.UrlEncode -> Uri.encode(query)
QueryEncoding.FormData -> URLEncoder.encode(query, "UTF-8")
QueryEncoding.None -> query
}
}
enum class QueryEncoding {
UrlEncode,
FormData,
None;
fun toInt(): Int {
return when (this) {
UrlEncode -> 0
FormData -> 1
None -> 2
}
}
companion object {
fun fromInt(value: Int?): QueryEncoding {
return when (value) {
1 -> FormData
2 -> None
else -> UrlEncode
}
}
}
}
} }