From 24e25148f0bed4f8e40fc5e1b8ae34295ec4db92 Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sun, 25 Sep 2022 20:59:29 +0200 Subject: [PATCH] Add multiple options for websearch query encoding Close #174 --- .../de/mm20/launcher2/backup/BackupManager.kt | 2 +- .../17.json | 450 ++++++++++++++++++ .../de/mm20/launcher2/database/AppDatabase.kt | 10 +- .../database/entities/WebsearchEntity.kt | 1 + i18n/src/main/res/values/strings.xml | 5 + .../launcher2/search/WebsearchRepository.kt | 2 + .../mm20/launcher2/search/data/Websearch.kt | 76 ++- .../component/preferences/ListPreference.kt | 2 + .../ui/component/preferences/Preference.kt | 25 +- .../websearch/WebSearchSettingsScreen.kt | 91 +++- 10 files changed, 627 insertions(+), 37 deletions(-) create mode 100644 database/schemas/de.mm20.launcher2.database.AppDatabase/17.json diff --git a/backup/src/main/java/de/mm20/launcher2/backup/BackupManager.kt b/backup/src/main/java/de/mm20/launcher2/backup/BackupManager.kt index d4d53c76..ff4acae8 100644 --- a/backup/src/main/java/de/mm20/launcher2/backup/BackupManager.kt +++ b/backup/src/main/java/de/mm20/launcher2/backup/BackupManager.kt @@ -185,7 +185,7 @@ class BackupManager( companion object { private const val BackupFormatMajor = 1 - private const val BackupFormatMinor = 1 + private const val BackupFormatMinor = 2 internal const val BackupFormat = "$BackupFormatMajor.$BackupFormatMinor" } } diff --git a/database/schemas/de.mm20.launcher2.database.AppDatabase/17.json b/database/schemas/de.mm20.launcher2.database.AppDatabase/17.json new file mode 100644 index 00000000..9bd1f109 --- /dev/null +++ b/database/schemas/de.mm20.launcher2.database.AppDatabase/17.json @@ -0,0 +1,450 @@ +{ + "formatVersion": 1, + "database": { + "version": 17, + "identityHash": "3321ba63cb650a091b1ca102198a41ba", + "entities": [ + { + "tableName": "forecasts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`timestamp` INTEGER NOT NULL, `temperature` REAL NOT NULL, `minTemp` REAL NOT NULL, `maxTemp` REAL NOT NULL, `pressure` REAL NOT NULL, `humidity` REAL NOT NULL, `icon` INTEGER NOT NULL, `condition` TEXT NOT NULL, `clouds` INTEGER NOT NULL, `windSpeed` REAL NOT NULL, `windDirection` REAL NOT NULL, `rain` REAL NOT NULL, `snow` REAL NOT NULL, `night` INTEGER NOT NULL, `location` TEXT NOT NULL, `provider` TEXT NOT NULL, `providerUrl` TEXT NOT NULL, `rainProbability` INTEGER NOT NULL, `snowProbability` INTEGER NOT NULL, `updateTime` INTEGER NOT NULL, PRIMARY KEY(`timestamp`))", + "fields": [ + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "temperature", + "columnName": "temperature", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "minTemp", + "columnName": "minTemp", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "maxTemp", + "columnName": "maxTemp", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "pressure", + "columnName": "pressure", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "humidity", + "columnName": "humidity", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "condition", + "columnName": "condition", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "clouds", + "columnName": "clouds", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "windSpeed", + "columnName": "windSpeed", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "windDirection", + "columnName": "windDirection", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "precipitation", + "columnName": "rain", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "snow", + "columnName": "snow", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "night", + "columnName": "night", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "location", + "columnName": "location", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "provider", + "columnName": "provider", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "providerUrl", + "columnName": "providerUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "precipProbability", + "columnName": "rainProbability", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "snowProbability", + "columnName": "snowProbability", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "updateTime", + "columnName": "updateTime", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "timestamp" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Searchable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `searchable` TEXT NOT NULL, `launchCount` INTEGER NOT NULL, `pinned` INTEGER NOT NULL, `hidden` INTEGER NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serializedSearchable", + "columnName": "searchable", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "launchCount", + "columnName": "launchCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "pinPosition", + "columnName": "pinned", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Websearch", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`urlTemplate` TEXT NOT NULL, `label` TEXT NOT NULL, `color` INTEGER NOT NULL, `icon` TEXT, `encoding` INTEGER, `id` INTEGER PRIMARY KEY AUTOINCREMENT)", + "fields": [ + { + "fieldPath": "urlTemplate", + "columnName": "urlTemplate", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "encoding", + "columnName": "encoding", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Currency", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`symbol` TEXT NOT NULL, `value` REAL NOT NULL, `lastUpdate` INTEGER NOT NULL, PRIMARY KEY(`symbol`))", + "fields": [ + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lastUpdate", + "columnName": "lastUpdate", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "symbol" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Icons", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `componentName` TEXT, `drawable` TEXT, `iconPack` TEXT NOT NULL, `scale` REAL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "componentName", + "columnName": "componentName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "drawable", + "columnName": "drawable", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "iconPack", + "columnName": "iconPack", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scale", + "columnName": "scale", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "IconPack", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `packageName` TEXT NOT NULL, `version` TEXT NOT NULL, `scale` REAL NOT NULL, PRIMARY KEY(`packageName`))", + "fields": [ + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scale", + "columnName": "scale", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "packageName" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Widget", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `data` TEXT NOT NULL, `height` INTEGER NOT NULL, `position` INTEGER NOT NULL, `label` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "height", + "columnName": "height", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CustomAttributes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `type` TEXT NOT NULL, `value` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3321ba63cb650a091b1ca102198a41ba')" + ] + } +} \ No newline at end of file diff --git a/database/src/main/java/de/mm20/launcher2/database/AppDatabase.kt b/database/src/main/java/de/mm20/launcher2/database/AppDatabase.kt index 59ea79ba..40a99c39 100644 --- a/database/src/main/java/de/mm20/launcher2/database/AppDatabase.kt +++ b/database/src/main/java/de/mm20/launcher2/database/AppDatabase.kt @@ -18,7 +18,7 @@ import de.mm20.launcher2.database.entities.* IconEntity::class, IconPackEntity::class, WidgetEntity::class, - CustomAttributeEntity::class], version = 16, exportSchema = true) + CustomAttributeEntity::class], version = 17, exportSchema = true) @TypeConverters(ComponentNameConverter::class, StringListConverter::class) abstract class AppDatabase : RoomDatabase() { @@ -61,6 +61,7 @@ abstract class AppDatabase : RoomDatabase() { Migration_13_14(), Migration_14_15(), Migration_15_16(), + Migration_16_17(), ).build() if (_instance == null) _instance = instance return instance @@ -165,4 +166,11 @@ class Migration_15_16 : Migration(15, 16) { ) """.trimIndent()) } +} + +class Migration_16_17 : Migration(16, 17) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE Websearch ADD COLUMN encoding INTEGER") + } + } \ No newline at end of file diff --git a/database/src/main/java/de/mm20/launcher2/database/entities/WebsearchEntity.kt b/database/src/main/java/de/mm20/launcher2/database/entities/WebsearchEntity.kt index 5a14a97a..482f2a79 100644 --- a/database/src/main/java/de/mm20/launcher2/database/entities/WebsearchEntity.kt +++ b/database/src/main/java/de/mm20/launcher2/database/entities/WebsearchEntity.kt @@ -9,5 +9,6 @@ data class WebsearchEntity( var label: String, var color: Int, var icon: String?, + var encoding: Int?, @PrimaryKey(autoGenerate = true) val id: Long? ) \ No newline at end of file diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 1db60712..4c3ce208 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -208,6 +208,11 @@ Custom icon Import from URL The given URL cannot be imported automatically. You can try a different URL or enter the required data manually. + Advanced + Query encoding + Percent-encoding + application/x-www-form-urlencoded + None Edit widgets Weather Calendar diff --git a/search/src/main/java/de/mm20/launcher2/search/WebsearchRepository.kt b/search/src/main/java/de/mm20/launcher2/search/WebsearchRepository.kt index 8b716411..6f02b060 100644 --- a/search/src/main/java/de/mm20/launcher2/search/WebsearchRepository.kt +++ b/search/src/main/java/de/mm20/launcher2/search/WebsearchRepository.kt @@ -244,6 +244,7 @@ internal class WebsearchRepositoryImpl( "label" to websearch.label, "template" to websearch.urlTemplate, "icon" to icon, + "encoding" to websearch.encoding, ) ) } @@ -289,6 +290,7 @@ internal class WebsearchRepositoryImpl( color = json.optInt("color", 0), label = json.getString("label"), icon = iconFile?.absolutePath, + encoding = json.optInt("encoding"), id = null ) websearches.add(entity) diff --git a/search/src/main/java/de/mm20/launcher2/search/data/Websearch.kt b/search/src/main/java/de/mm20/launcher2/search/data/Websearch.kt index 3a14bfcf..5e44d9de 100644 --- a/search/src/main/java/de/mm20/launcher2/search/data/Websearch.kt +++ b/search/src/main/java/de/mm20/launcher2/search/data/Websearch.kt @@ -3,41 +3,79 @@ package de.mm20.launcher2.search.data import android.content.Intent import android.net.Uri import de.mm20.launcher2.database.entities.WebsearchEntity +import java.net.URLEncoder +import java.nio.charset.Charset +import java.nio.charset.StandardCharsets class Websearch( - var urlTemplate: String, - var label: String, - var color: Int, - var icon: String?, - var id: Long? = null, - val query: String? = null + var urlTemplate: String, + var label: String, + var color: Int, + var icon: String?, + var id: Long? = null, + var encoding: QueryEncoding = QueryEncoding.UrlEncode, + val query: String? = null, ) { constructor(entity: WebsearchEntity, query: String? = null) : this( - urlTemplate = entity.urlTemplate, - label = entity.label, - icon = entity.icon, - color = entity.color, - id = entity.id, - query = query + urlTemplate = entity.urlTemplate, + label = entity.label, + icon = entity.icon, + color = entity.color, + id = entity.id, + query = query, + encoding = QueryEncoding.fromInt(entity.encoding) ) fun toDatabaseEntity(): WebsearchEntity { return WebsearchEntity( - urlTemplate = urlTemplate, - color = color, - icon = icon, - label = label, - id = id + urlTemplate = urlTemplate, + color = color, + icon = icon, + label = label, + id = id, + encoding = encoding.toInt() ) } fun getLaunchIntent(): Intent? { - if(query == null) return null + if (query == null) return null val intent = Intent(Intent.ACTION_VIEW) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - val url = urlTemplate.replace("\${1}", Uri.encode(query)) + val url = urlTemplate.replace("\${1}", encodeQuery(query, encoding)) intent.data = Uri.parse(url) return intent } + + 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 + } + } + } + } } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/ListPreference.kt b/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/ListPreference.kt index 8558af7d..9471c1af 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/ListPreference.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/ListPreference.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.window.Dialog fun ListPreference( title: String, icon: ImageVector? = null, + iconPadding: Boolean = true, items: List>, value: T, summary: String? = items.firstOrNull { value == it.value }?.label, @@ -33,6 +34,7 @@ fun ListPreference( title = title, summary = summary, icon = icon, + iconPadding = iconPadding, enabled = enabled, onClick = { showDialog = true diff --git a/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/Preference.kt b/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/Preference.kt index d9a4ec5e..f985349a 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/Preference.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/Preference.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.unit.dp fun Preference( title: String, icon: @Composable (() -> Unit), + iconPadding: Boolean = true, summary: String? = null, onClick: () -> Unit = {}, controls: @Composable (() -> Unit)? = null, @@ -29,13 +30,17 @@ fun Preference( .padding(horizontal = 16.dp) .alpha(if (enabled) 1f else 0.38f), ) { - Box( - modifier = Modifier - .width(56.dp) - .padding(start = 4.dp), - contentAlignment = Alignment.CenterStart - ) { - icon() + if (iconPadding) { + Box( + modifier = Modifier + .width(56.dp) + .padding(start = 4.dp), + contentAlignment = Alignment.CenterStart + ) { + icon() + } + } else { + Box(modifier = Modifier.size(0.dp)) } Column( modifier = Modifier.weight(1f).padding(vertical = 16.dp) @@ -63,13 +68,15 @@ fun Preference( fun Preference( title: String, icon: ImageVector? = null, + iconPadding: Boolean = true, summary: String? = null, onClick: () -> Unit = {}, controls: @Composable (() -> Unit)? = null, enabled: Boolean = true ) { Preference( - title, icon = { + title, + icon = { if (icon != null) { Icon( imageVector = icon, @@ -77,6 +84,6 @@ fun Preference( tint = MaterialTheme.colorScheme.primary, ) } - }, summary, onClick, controls, enabled + }, iconPadding, summary, onClick, controls, enabled ) } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/websearch/WebSearchSettingsScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/websearch/WebSearchSettingsScreen.kt index 88f9a1d1..24f9c82b 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/websearch/WebSearchSettingsScreen.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/websearch/WebSearchSettingsScreen.kt @@ -3,22 +3,65 @@ package de.mm20.launcher2.ui.settings.websearch import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.* -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material.icons.rounded.Add +import androidx.compose.material.icons.rounded.ArrowForward +import androidx.compose.material.icons.rounded.Check +import androidx.compose.material.icons.rounded.CloudDownload +import androidx.compose.material.icons.rounded.MoreVert +import androidx.compose.material.icons.rounded.Search +import androidx.compose.material.icons.rounded.Tag +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Divider +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TextField +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.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -28,6 +71,7 @@ import com.godaddy.android.colorpicker.ClassicColorPicker import de.mm20.launcher2.search.data.Websearch import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.BottomSheetDialog +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 @@ -146,6 +190,7 @@ fun EditWebsearchDialog( var label by remember { mutableStateOf(value.label) } var showError by remember { mutableStateOf(false) } var urlTemplate by remember { mutableStateOf(value.urlTemplate) } + var encoding by remember { mutableStateOf(value.encoding) } var color by remember { mutableStateOf(value.color) } var icon by remember { mutableStateOf(value.icon) } @@ -226,6 +271,7 @@ fun EditWebsearchDialog( } value.icon = icon value.color = color + value.encoding = encoding onValueSaved(value) } else { showError = true @@ -426,6 +472,37 @@ fun EditWebsearchDialog( text = stringResource(R.string.websearch_dialog_url_description), style = MaterialTheme.typography.labelMedium ) + + var showAdvanced by remember { mutableStateOf(false) } + + AnimatedVisibility(!showAdvanced) { + TextButton( + modifier = Modifier.padding(vertical = 16.dp).align(Alignment.End), + onClick = { showAdvanced = true }) { + Text(stringResource(R.string.websearch_dialog_advanced)) + } + } + + AnimatedVisibility(showAdvanced) { + Column( + modifier = Modifier.padding(top = 16.dp) + ) { + Divider() + ListPreference( + title = stringResource(R.string.websearch_dialog_query_endcoding), + items = listOf( + stringResource(R.string.websearch_dialog_query_endcoding_url) to Websearch.QueryEncoding.UrlEncode, + stringResource(R.string.websearch_dialog_query_endcoding_form) to Websearch.QueryEncoding.FormData, + stringResource(R.string.websearch_dialog_query_endcoding_none) to Websearch.QueryEncoding.None, + ), + iconPadding = false, + value = encoding, + onValueChanged = { + encoding = it + } + ) + } + } } } }