Make search action order customizable
This commit is contained in:
parent
960c3b70f5
commit
3463b3b800
@ -2,7 +2,7 @@
|
|||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 19,
|
"version": 19,
|
||||||
"identityHash": "c6adf1dca8ade5117d4e8952a67786fa",
|
"identityHash": "968a73b13f39e00505a4adf1bda01394",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "forecasts",
|
"tableName": "forecasts",
|
||||||
@ -398,7 +398,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "SearchAction",
|
"tableName": "SearchAction",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`position` INTEGER NOT NULL, `type` TEXT NOT NULL, `data` TEXT NOT NULL, `label` TEXT, `icon` INTEGER NOT NULL, `color` INTEGER NOT NULL, `customIcon` TEXT, `options` TEXT, PRIMARY KEY(`position`))",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`position` INTEGER NOT NULL, `type` TEXT NOT NULL, `data` TEXT, `label` TEXT, `icon` INTEGER, `color` INTEGER, `customIcon` TEXT, `options` TEXT, PRIMARY KEY(`position`))",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "position",
|
"fieldPath": "position",
|
||||||
@ -416,7 +416,7 @@
|
|||||||
"fieldPath": "data",
|
"fieldPath": "data",
|
||||||
"columnName": "data",
|
"columnName": "data",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": true
|
"notNull": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "label",
|
"fieldPath": "label",
|
||||||
@ -428,13 +428,13 @@
|
|||||||
"fieldPath": "icon",
|
"fieldPath": "icon",
|
||||||
"columnName": "icon",
|
"columnName": "icon",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER",
|
||||||
"notNull": true
|
"notNull": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "color",
|
"fieldPath": "color",
|
||||||
"columnName": "color",
|
"columnName": "color",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER",
|
||||||
"notNull": true
|
"notNull": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "customIcon",
|
"fieldPath": "customIcon",
|
||||||
@ -462,7 +462,7 @@
|
|||||||
"views": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"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, 'c6adf1dca8ade5117d4e8952a67786fa')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '968a73b13f39e00505a4adf1bda01394')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,13 +57,23 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
.addCallback(object : Callback() {
|
.addCallback(object : Callback() {
|
||||||
override fun onCreate(db: SupportSQLiteDatabase) {
|
override fun onCreate(db: SupportSQLiteDatabase) {
|
||||||
super.onCreate(db)
|
super.onCreate(db)
|
||||||
|
db.execSQL("INSERT INTO `SearchAction` (`position`, `type`) VALUES" +
|
||||||
|
"(0, 'call')," +
|
||||||
|
"(1, 'message')," +
|
||||||
|
"(2, 'email')," +
|
||||||
|
"(3, 'contact')," +
|
||||||
|
"(4, 'alarm')," +
|
||||||
|
"(5, 'timer')," +
|
||||||
|
"(6, 'calendar')," +
|
||||||
|
"(7, 'website')"
|
||||||
|
)
|
||||||
|
|
||||||
db.execSQL("INSERT INTO `SearchAction` (`position`, `type`, `data`, `label`, `color`, `icon`, `customIcon`, `options`) " +
|
db.execSQL("INSERT INTO `SearchAction` (`position`, `type`, `data`, `label`, `color`, `icon`, `customIcon`, `options`) " +
|
||||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?)",
|
"VALUES (?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
arrayOf(
|
arrayOf(
|
||||||
0, "url", context.getString(R.string.default_websearch_1_url), context.getString(R.string.default_websearch_1_name), 0, 0, null, null,
|
8, "url", context.getString(R.string.default_websearch_1_url), context.getString(R.string.default_websearch_1_name), 0, 0, null, null,
|
||||||
0, "url", context.getString(R.string.default_websearch_2_url), context.getString(R.string.default_websearch_2_name), 0, 0, null, null,
|
9, "url", context.getString(R.string.default_websearch_2_url), context.getString(R.string.default_websearch_2_name), 0, 0, null, null,
|
||||||
0, "url", context.getString(R.string.default_websearch_3_url), context.getString(R.string.default_websearch_3_name), 0, 0, null, null,
|
10, "url", context.getString(R.string.default_websearch_3_url), context.getString(R.string.default_websearch_3_name), 0, 0, null, null,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
package de.mm20.launcher2.database
|
package de.mm20.launcher2.database
|
||||||
|
|
||||||
import androidx.room.Dao
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
import de.mm20.launcher2.database.entities.SearchActionEntity
|
import de.mm20.launcher2.database.entities.SearchActionEntity
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@ -9,4 +11,16 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
interface SearchActionDao {
|
interface SearchActionDao {
|
||||||
@Query("SELECT * FROM SearchAction ORDER BY position ASC")
|
@Query("SELECT * FROM SearchAction ORDER BY position ASC")
|
||||||
fun getSearchActions(): Flow<List<SearchActionEntity>>
|
fun getSearchActions(): Flow<List<SearchActionEntity>>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
suspend fun replaceAll(actions: List<SearchActionEntity>) {
|
||||||
|
deleteAll()
|
||||||
|
insertAll(actions)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("DELETE FROM `SearchAction`")
|
||||||
|
suspend fun deleteAll()
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
suspend fun insertAll(actions: List<SearchActionEntity>)
|
||||||
}
|
}
|
||||||
@ -7,10 +7,10 @@ import androidx.room.PrimaryKey
|
|||||||
data class SearchActionEntity(
|
data class SearchActionEntity(
|
||||||
@PrimaryKey val position: Int,
|
@PrimaryKey val position: Int,
|
||||||
val type: String,
|
val type: String,
|
||||||
val data: String,
|
val data: String? = null,
|
||||||
val label: String?,
|
val label: String? = null,
|
||||||
val icon: Int,
|
val icon: Int? = null,
|
||||||
val color: Int = 0,
|
val color: Int? = null,
|
||||||
val customIcon: String? = null,
|
val customIcon: String? = null,
|
||||||
val options: String? = null,
|
val options: String? = null,
|
||||||
)
|
)
|
||||||
@ -9,9 +9,19 @@ class Migration_18_19 : Migration(18, 19) {
|
|||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
val websearches =
|
val websearches =
|
||||||
database.query("SELECT label, urlTemplate, color, icon, encoding FROM `Websearch` ORDER BY label ASC")
|
database.query("SELECT label, urlTemplate, color, icon, encoding FROM `Websearch` ORDER BY label ASC")
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS `SearchAction` (`position` INTEGER NOT NULL, `type` TEXT NOT NULL, `data` TEXT NOT NULL, `label` TEXT, `icon` INTEGER NOT NULL, `color` INTEGER NOT NULL, `customIcon` TEXT, `options` TEXT, PRIMARY KEY(`position`))"
|
database.execSQL("CREATE TABLE IF NOT EXISTS `SearchAction` (`position` INTEGER NOT NULL, `type` TEXT NOT NULL, `data` TEXT, `label` TEXT, `icon` INTEGER, `color` INTEGER, `customIcon` TEXT, `options` TEXT, PRIMARY KEY(`position`))"
|
||||||
)
|
)
|
||||||
var position = 0
|
database.execSQL("INSERT INTO `SearchAction` (`position`, `type`) VALUES" +
|
||||||
|
"(0, 'call')," +
|
||||||
|
"(1, 'message')," +
|
||||||
|
"(2, 'email')," +
|
||||||
|
"(3, 'contact')," +
|
||||||
|
"(4, 'alarm')," +
|
||||||
|
"(5, 'timer')," +
|
||||||
|
"(6, 'calendar')," +
|
||||||
|
"(7, 'website')"
|
||||||
|
)
|
||||||
|
var position = 8
|
||||||
while (websearches.moveToNext()) {
|
while (websearches.moveToNext()) {
|
||||||
val label = websearches.getString(0)
|
val label = websearches.getString(0)
|
||||||
val data = websearches.getString(1)
|
val data = websearches.getString(1)
|
||||||
|
|||||||
@ -22,7 +22,7 @@ internal val Context.dataStore: LauncherDataStore by dataStore(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
internal const val SchemaVersion = 12
|
internal const val SchemaVersion = 11
|
||||||
|
|
||||||
internal fun getMigrations(context: Context): List<DataMigration<Settings>> {
|
internal fun getMigrations(context: Context): List<DataMigration<Settings>> {
|
||||||
return listOf(
|
return listOf(
|
||||||
@ -37,6 +37,5 @@ internal fun getMigrations(context: Context): List<DataMigration<Settings>> {
|
|||||||
Migration_8_9(),
|
Migration_8_9(),
|
||||||
Migration_9_10(),
|
Migration_9_10(),
|
||||||
Migration_10_11(),
|
Migration_10_11(),
|
||||||
Migration_11_12(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -162,17 +162,6 @@ fun createFactorySettings(context: Context): Settings {
|
|||||||
Settings.WidgetSettings.newBuilder()
|
Settings.WidgetSettings.newBuilder()
|
||||||
.setEditButton(true)
|
.setEditButton(true)
|
||||||
)
|
)
|
||||||
.setSearchActions(
|
|
||||||
Settings.SearchActionSettings.newBuilder()
|
|
||||||
.setCall(true)
|
|
||||||
.setContact(true)
|
|
||||||
.setEmail(true)
|
|
||||||
.setMessage(true)
|
|
||||||
.setOpenUrl(true)
|
|
||||||
.setScheduleEvent(true)
|
|
||||||
.setSetAlarm(true)
|
|
||||||
.setStartTimer(true)
|
|
||||||
)
|
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -288,16 +288,4 @@ message Settings {
|
|||||||
bool edit_button = 1;
|
bool edit_button = 1;
|
||||||
}
|
}
|
||||||
WidgetSettings widgets = 26;
|
WidgetSettings widgets = 26;
|
||||||
|
|
||||||
message SearchActionSettings {
|
|
||||||
bool call = 1;
|
|
||||||
bool message = 2;
|
|
||||||
bool email = 3;
|
|
||||||
bool contact = 4;
|
|
||||||
bool open_url = 5;
|
|
||||||
bool schedule_event = 6;
|
|
||||||
bool set_alarm = 7;
|
|
||||||
bool start_timer = 8;
|
|
||||||
}
|
|
||||||
SearchActionSettings search_actions = 27;
|
|
||||||
}
|
}
|
||||||
@ -5,10 +5,21 @@ import de.mm20.launcher2.crashreporter.CrashReporter
|
|||||||
import de.mm20.launcher2.database.AppDatabase
|
import de.mm20.launcher2.database.AppDatabase
|
||||||
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.builders.CallActionBuilder
|
||||||
|
import de.mm20.launcher2.searchactions.builders.CreateContactActionBuilder
|
||||||
|
import de.mm20.launcher2.searchactions.builders.EmailActionBuilder
|
||||||
|
import de.mm20.launcher2.searchactions.builders.MessageActionBuilder
|
||||||
|
import de.mm20.launcher2.searchactions.builders.OpenUrlActionBuilder
|
||||||
|
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.TimerActionBuilder
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
@ -17,6 +28,9 @@ import java.util.UUID
|
|||||||
|
|
||||||
interface SearchActionRepository {
|
interface SearchActionRepository {
|
||||||
fun getSearchActionBuilders(): Flow<List<SearchActionBuilder>>
|
fun getSearchActionBuilders(): Flow<List<SearchActionBuilder>>
|
||||||
|
fun getBuiltinSearchActionBuilders(): List<SearchActionBuilder>
|
||||||
|
|
||||||
|
fun saveSearchActionBuilders(builders: List<SearchActionBuilder>)
|
||||||
|
|
||||||
suspend fun export(toDir: File)
|
suspend fun export(toDir: File)
|
||||||
suspend fun import(fromDir: File)
|
suspend fun import(fromDir: File)
|
||||||
@ -26,9 +40,35 @@ internal class SearchActionRepositoryImpl(
|
|||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val database: AppDatabase
|
private val database: AppDatabase
|
||||||
): SearchActionRepository {
|
): SearchActionRepository {
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||||
override fun getSearchActionBuilders(): Flow<List<SearchActionBuilder>> {
|
override fun getSearchActionBuilders(): Flow<List<SearchActionBuilder>> {
|
||||||
val dao = database.searchActionDao()
|
val dao = database.searchActionDao()
|
||||||
return dao.getSearchActions().map { it.mapNotNull { SearchActionBuilder.from(it) } }
|
return dao.getSearchActions().map { it.mapNotNull { SearchActionBuilder.from(context, it) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBuiltinSearchActionBuilders(): List<SearchActionBuilder> {
|
||||||
|
val allActions = listOf(
|
||||||
|
CallActionBuilder(context),
|
||||||
|
MessageActionBuilder(context),
|
||||||
|
CreateContactActionBuilder(context),
|
||||||
|
EmailActionBuilder(context),
|
||||||
|
ScheduleEventActionBuilder(context),
|
||||||
|
SetAlarmActionBuilder(context),
|
||||||
|
TimerActionBuilder(context),
|
||||||
|
OpenUrlActionBuilder(context),
|
||||||
|
)
|
||||||
|
|
||||||
|
return allActions
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveSearchActionBuilders(builders: List<SearchActionBuilder>) {
|
||||||
|
scope.launch {
|
||||||
|
val dao = database.searchActionDao()
|
||||||
|
dao.replaceAll(
|
||||||
|
builders.mapIndexed { i, it -> SearchActionBuilder.toDatabaseEntity(it, i) }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun export(toDir: File) = withContext(Dispatchers.IO) {
|
override suspend fun export(toDir: File) = withContext(Dispatchers.IO) {
|
||||||
|
|||||||
@ -2,13 +2,9 @@ package de.mm20.launcher2.searchactions
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.LauncherActivityInfo
|
|
||||||
import android.content.pm.LauncherApps
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.content.pm.ResolveInfo
|
import android.content.pm.ResolveInfo
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.UserHandle
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Xml
|
import android.util.Xml
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
@ -16,20 +12,10 @@ import coil.imageLoader
|
|||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import coil.size.Scale
|
import coil.size.Scale
|
||||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
import de.mm20.launcher2.database.entities.WebsearchEntity
|
|
||||||
import de.mm20.launcher2.ktx.jsonObjectOf
|
|
||||||
import de.mm20.launcher2.preferences.Settings.SearchActionSettings
|
|
||||||
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.CallActionBuilder
|
import de.mm20.launcher2.searchactions.builders.CallActionBuilder
|
||||||
import de.mm20.launcher2.searchactions.builders.CreateContactActionBuilder
|
|
||||||
import de.mm20.launcher2.searchactions.builders.EmailActionBuilder
|
|
||||||
import de.mm20.launcher2.searchactions.builders.MessageActionBuilder
|
|
||||||
import de.mm20.launcher2.searchactions.builders.OpenUrlActionBuilder
|
|
||||||
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.TimerActionBuilder
|
|
||||||
import de.mm20.launcher2.searchactions.builders.WebsearchActionBuilder
|
import de.mm20.launcher2.searchactions.builders.WebsearchActionBuilder
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
@ -42,8 +28,6 @@ import kotlinx.coroutines.flow.map
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.json.JSONArray
|
|
||||||
import org.json.JSONException
|
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import org.xmlpull.v1.XmlPullParser
|
import org.xmlpull.v1.XmlPullParser
|
||||||
import org.xmlpull.v1.XmlPullParserException
|
import org.xmlpull.v1.XmlPullParserException
|
||||||
@ -54,12 +38,18 @@ import java.net.URL
|
|||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
interface SearchActionService {
|
interface SearchActionService {
|
||||||
fun search(settings: SearchActionSettings, query: String): Flow<ImmutableList<SearchAction>>
|
fun search(query: String): Flow<ImmutableList<SearchAction>>
|
||||||
|
|
||||||
|
fun getSearchActionBuilders(): Flow<List<SearchActionBuilder>>
|
||||||
|
fun getDisabledActionBuilders(): Flow<List<SearchActionBuilder>>
|
||||||
|
|
||||||
|
fun saveSearchActionBuilders(builders: List<SearchActionBuilder>)
|
||||||
|
|
||||||
suspend fun importWebsearch(url: String, iconSize: Int): WebsearchActionBuilder?
|
suspend fun importWebsearch(url: String, iconSize: Int): WebsearchActionBuilder?
|
||||||
suspend fun createIcon(uri: Uri, size: Int): String?
|
|
||||||
|
|
||||||
suspend fun getSearchActivities(): List<ResolveInfo>
|
suspend fun getSearchActivities(): List<ResolveInfo>
|
||||||
|
|
||||||
|
suspend fun createIcon(uri: Uri, size: Int): String?
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class SearchActionServiceImpl(
|
internal class SearchActionServiceImpl(
|
||||||
@ -68,7 +58,6 @@ internal class SearchActionServiceImpl(
|
|||||||
private val textClassifier: TextClassifier,
|
private val textClassifier: TextClassifier,
|
||||||
) : SearchActionService {
|
) : SearchActionService {
|
||||||
override fun search(
|
override fun search(
|
||||||
settings: SearchActionSettings,
|
|
||||||
query: String
|
query: String
|
||||||
): Flow<ImmutableList<SearchAction>> = flow {
|
): Flow<ImmutableList<SearchAction>> = flow {
|
||||||
if (query.isBlank()) {
|
if (query.isBlank()) {
|
||||||
@ -76,28 +65,33 @@ internal class SearchActionServiceImpl(
|
|||||||
return@flow
|
return@flow
|
||||||
}
|
}
|
||||||
|
|
||||||
val builders = mutableListOf<SearchActionBuilder>()
|
|
||||||
|
|
||||||
if (settings.call) builders.add(CallActionBuilder)
|
|
||||||
if (settings.message) builders.add(MessageActionBuilder)
|
|
||||||
if (settings.contact) builders.add(CreateContactActionBuilder)
|
|
||||||
if (settings.email) builders.add(EmailActionBuilder)
|
|
||||||
if (settings.openUrl) builders.add(OpenUrlActionBuilder)
|
|
||||||
if (settings.scheduleEvent) builders.add(ScheduleEventActionBuilder)
|
|
||||||
if (settings.setAlarm) builders.add(SetAlarmActionBuilder)
|
|
||||||
if (settings.startTimer) builders.add(TimerActionBuilder)
|
|
||||||
|
|
||||||
val classificationResult = textClassifier.classify(context, query)
|
val classificationResult = textClassifier.classify(context, query)
|
||||||
|
|
||||||
val other = repository.getSearchActionBuilders()
|
val builders = repository.getSearchActionBuilders()
|
||||||
|
|
||||||
emitAll(
|
emitAll(
|
||||||
other.map {
|
builders.map {
|
||||||
(builders + it).mapNotNull { it.build(context, classificationResult) }.toImmutableList()
|
it.mapNotNull { it.build(context, classificationResult) }.toImmutableList()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getSearchActionBuilders(): Flow<List<SearchActionBuilder>> {
|
||||||
|
return repository.getSearchActionBuilders()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDisabledActionBuilders(): Flow<List<SearchActionBuilder>> {
|
||||||
|
val allActions = repository.getBuiltinSearchActionBuilders()
|
||||||
|
|
||||||
|
return getSearchActionBuilders().map { enabled ->
|
||||||
|
allActions.filter { action -> !enabled.any { it.key == action.key } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveSearchActionBuilders(builders: List<SearchActionBuilder>) {
|
||||||
|
repository.saveSearchActionBuilders(builders)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun importWebsearch(url: String, iconSize: Int): WebsearchActionBuilder? =
|
override suspend fun importWebsearch(url: String, iconSize: Int): WebsearchActionBuilder? =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1,21 +1,27 @@
|
|||||||
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.pm.LauncherActivityInfo
|
|
||||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||||
import de.mm20.launcher2.searchactions.TextType
|
import de.mm20.launcher2.searchactions.TextType
|
||||||
import de.mm20.launcher2.searchactions.actions.AppSearchAction
|
import de.mm20.launcher2.searchactions.actions.AppSearchAction
|
||||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||||
|
import de.mm20.launcher2.searchactions.actions.SearchActionIcon
|
||||||
|
|
||||||
class AppSearchActionBuilder(
|
class AppSearchActionBuilder(
|
||||||
val label: String,
|
override val label: String,
|
||||||
val activity: LauncherActivityInfo,
|
val componentName: ComponentName,
|
||||||
val filter: TextType? = null,
|
override val icon: SearchActionIcon = SearchActionIcon.Search,
|
||||||
) : SearchActionBuilder {
|
override val iconColor: Int = 0,
|
||||||
|
override val customIcon: String? = null,
|
||||||
|
) : CustomizableSearchActionBuilder {
|
||||||
|
|
||||||
|
override val key: String
|
||||||
|
get() = "app://${componentName.flattenToShortString()}"
|
||||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||||
return AppSearchAction(
|
return AppSearchAction(
|
||||||
label = label,
|
label = label,
|
||||||
componentName = activity.componentName,
|
componentName = componentName,
|
||||||
query = classifiedQuery.text,
|
query = classifiedQuery.text,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,18 @@ import de.mm20.launcher2.searchactions.R
|
|||||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||||
import de.mm20.launcher2.searchactions.actions.CallAction
|
import de.mm20.launcher2.searchactions.actions.CallAction
|
||||||
|
import de.mm20.launcher2.searchactions.actions.SearchActionIcon
|
||||||
|
|
||||||
object CallActionBuilder: SearchActionBuilder {
|
class CallActionBuilder(
|
||||||
|
override val label: String
|
||||||
|
): SearchActionBuilder {
|
||||||
|
|
||||||
|
constructor(context: Context): this(context.getString(R.string.search_action_call))
|
||||||
|
|
||||||
|
override val key: String
|
||||||
|
get() = "call"
|
||||||
|
|
||||||
|
override val icon: SearchActionIcon = SearchActionIcon.Phone
|
||||||
|
|
||||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||||
if (classifiedQuery.phoneNumber != null) {
|
if (classifiedQuery.phoneNumber != null) {
|
||||||
|
|||||||
@ -5,8 +5,18 @@ import de.mm20.launcher2.searchactions.R
|
|||||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||||
import de.mm20.launcher2.searchactions.actions.CreateContactAction
|
import de.mm20.launcher2.searchactions.actions.CreateContactAction
|
||||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||||
|
import de.mm20.launcher2.searchactions.actions.SearchActionIcon
|
||||||
|
|
||||||
object CreateContactActionBuilder : SearchActionBuilder {
|
class CreateContactActionBuilder(
|
||||||
|
override val label: String
|
||||||
|
) : SearchActionBuilder {
|
||||||
|
|
||||||
|
constructor(context: Context) : this(context.getString(R.string.search_action_contact))
|
||||||
|
|
||||||
|
override val key: String
|
||||||
|
get() = "contact"
|
||||||
|
|
||||||
|
override val icon: SearchActionIcon = SearchActionIcon.Contact
|
||||||
|
|
||||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||||
if (classifiedQuery.phoneNumber != null || classifiedQuery.email != null) {
|
if (classifiedQuery.phoneNumber != null || classifiedQuery.email != null) {
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
package de.mm20.launcher2.searchactions.builders
|
||||||
|
|
||||||
|
sealed interface CustomizableSearchActionBuilder: SearchActionBuilder
|
||||||
@ -5,8 +5,18 @@ import de.mm20.launcher2.searchactions.R
|
|||||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||||
import de.mm20.launcher2.searchactions.actions.EmailAction
|
import de.mm20.launcher2.searchactions.actions.EmailAction
|
||||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||||
|
import de.mm20.launcher2.searchactions.actions.SearchActionIcon
|
||||||
|
|
||||||
object EmailActionBuilder: SearchActionBuilder {
|
class EmailActionBuilder(
|
||||||
|
override val label: String
|
||||||
|
): SearchActionBuilder {
|
||||||
|
|
||||||
|
constructor(context: Context) : this(context.getString(R.string.search_action_email))
|
||||||
|
|
||||||
|
override val key: String
|
||||||
|
get() = "email"
|
||||||
|
|
||||||
|
override val icon: SearchActionIcon = SearchActionIcon.Email
|
||||||
|
|
||||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||||
if (classifiedQuery.email != null) {
|
if (classifiedQuery.email != null) {
|
||||||
|
|||||||
@ -5,8 +5,18 @@ import de.mm20.launcher2.searchactions.R
|
|||||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||||
import de.mm20.launcher2.searchactions.actions.MessageAction
|
import de.mm20.launcher2.searchactions.actions.MessageAction
|
||||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||||
|
import de.mm20.launcher2.searchactions.actions.SearchActionIcon
|
||||||
|
|
||||||
object MessageActionBuilder: SearchActionBuilder {
|
class MessageActionBuilder(
|
||||||
|
override val label: String
|
||||||
|
): SearchActionBuilder {
|
||||||
|
|
||||||
|
constructor(context: Context) : this(context.getString(R.string.search_action_message))
|
||||||
|
|
||||||
|
override val key: String
|
||||||
|
get() = "message"
|
||||||
|
|
||||||
|
override val icon: SearchActionIcon = SearchActionIcon.Message
|
||||||
|
|
||||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||||
if (classifiedQuery.phoneNumber != null) {
|
if (classifiedQuery.phoneNumber != null) {
|
||||||
|
|||||||
@ -6,8 +6,18 @@ import de.mm20.launcher2.searchactions.TextClassificationResult
|
|||||||
import de.mm20.launcher2.searchactions.actions.MessageAction
|
import de.mm20.launcher2.searchactions.actions.MessageAction
|
||||||
import de.mm20.launcher2.searchactions.actions.OpenUrlAction
|
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
|
||||||
|
|
||||||
object OpenUrlActionBuilder : SearchActionBuilder {
|
class OpenUrlActionBuilder(
|
||||||
|
override val label: String
|
||||||
|
) : SearchActionBuilder {
|
||||||
|
|
||||||
|
constructor(context: Context) : this(context.getString(R.string.search_action_open_url))
|
||||||
|
|
||||||
|
override val key: String
|
||||||
|
get() = "website"
|
||||||
|
|
||||||
|
override val icon: SearchActionIcon = SearchActionIcon.Website
|
||||||
|
|
||||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||||
if (classifiedQuery.url != null) {
|
if (classifiedQuery.url != null) {
|
||||||
|
|||||||
@ -6,9 +6,19 @@ import de.mm20.launcher2.searchactions.TextClassificationResult
|
|||||||
import de.mm20.launcher2.searchactions.actions.MessageAction
|
import de.mm20.launcher2.searchactions.actions.MessageAction
|
||||||
import de.mm20.launcher2.searchactions.actions.ScheduleEventAction
|
import de.mm20.launcher2.searchactions.actions.ScheduleEventAction
|
||||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||||
|
import de.mm20.launcher2.searchactions.actions.SearchActionIcon
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
object ScheduleEventActionBuilder : SearchActionBuilder {
|
class ScheduleEventActionBuilder(
|
||||||
|
override val label: String
|
||||||
|
) : SearchActionBuilder {
|
||||||
|
|
||||||
|
constructor(context: Context) : this(context.getString(R.string.search_action_event))
|
||||||
|
|
||||||
|
override val key: String
|
||||||
|
get() = "calendar"
|
||||||
|
|
||||||
|
override val icon: SearchActionIcon = SearchActionIcon.Calendar
|
||||||
|
|
||||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||||
if (classifiedQuery.date != null) {
|
if (classifiedQuery.date != null) {
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
package de.mm20.launcher2.searchactions.builders
|
package de.mm20.launcher2.searchactions.builders
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
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.searchactions.TextClassificationResult
|
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||||
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
|
||||||
@ -9,10 +11,18 @@ import org.json.JSONException
|
|||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
interface SearchActionBuilder {
|
interface SearchActionBuilder {
|
||||||
|
val label: String
|
||||||
|
val icon: SearchActionIcon
|
||||||
|
val iconColor: Int
|
||||||
|
get() = 0
|
||||||
|
val customIcon: String?
|
||||||
|
get() = null
|
||||||
|
|
||||||
|
val key: String
|
||||||
fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction?
|
fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction?
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun from(entity: SearchActionEntity): SearchActionBuilder? {
|
internal fun from(context: Context, entity: SearchActionEntity): SearchActionBuilder? {
|
||||||
val options = entity.options?.let {
|
val options = entity.options?.let {
|
||||||
try {
|
try {
|
||||||
JSONObject(it)
|
JSONObject(it)
|
||||||
@ -24,8 +34,8 @@ interface SearchActionBuilder {
|
|||||||
"url" -> {
|
"url" -> {
|
||||||
return WebsearchActionBuilder(
|
return WebsearchActionBuilder(
|
||||||
label = entity.label ?: "",
|
label = entity.label ?: "",
|
||||||
urlTemplate = entity.data,
|
urlTemplate = entity.data ?: return null,
|
||||||
color = entity.color,
|
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 = WebsearchActionBuilder.QueryEncoding.fromInt(options?.optInt("encoding"))
|
||||||
@ -34,9 +44,38 @@ interface SearchActionBuilder {
|
|||||||
"app" -> {
|
"app" -> {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
"call" -> return CallActionBuilder(context)
|
||||||
|
"message" -> return MessageActionBuilder(context)
|
||||||
|
"email" -> return EmailActionBuilder(context)
|
||||||
|
"contact" -> return CreateContactActionBuilder(context)
|
||||||
|
"alarm" -> return SetAlarmActionBuilder(context)
|
||||||
|
"timer" -> return TimerActionBuilder(context)
|
||||||
|
"calendar" -> return ScheduleEventActionBuilder(context)
|
||||||
|
"website" -> return OpenUrlActionBuilder(context)
|
||||||
else -> return null
|
else -> return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
internal fun toDatabaseEntity(builder: SearchActionBuilder, position: Int): SearchActionEntity {
|
||||||
|
return when(builder) {
|
||||||
|
is WebsearchActionBuilder -> SearchActionEntity(
|
||||||
|
position = position,
|
||||||
|
type = "url",
|
||||||
|
label = builder.label,
|
||||||
|
data = builder.urlTemplate,
|
||||||
|
color = builder.iconColor,
|
||||||
|
icon = builder.icon.toInt(),
|
||||||
|
customIcon = builder.customIcon,
|
||||||
|
options = jsonObjectOf(
|
||||||
|
"encoding" to builder.encoding.toInt()
|
||||||
|
).toString()
|
||||||
|
)
|
||||||
|
//is AppSearchActionBuilder -> null
|
||||||
|
else -> SearchActionEntity(
|
||||||
|
position = position,
|
||||||
|
type = builder.key,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,10 +4,20 @@ import android.content.Context
|
|||||||
import de.mm20.launcher2.searchactions.R
|
import de.mm20.launcher2.searchactions.R
|
||||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||||
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.SetAlarmAction
|
import de.mm20.launcher2.searchactions.actions.SetAlarmAction
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
object SetAlarmActionBuilder : SearchActionBuilder {
|
class SetAlarmActionBuilder(
|
||||||
|
override val label: String
|
||||||
|
) : SearchActionBuilder {
|
||||||
|
|
||||||
|
constructor(context: Context) : this(context.getString(R.string.search_action_alarm))
|
||||||
|
|
||||||
|
override val key: String
|
||||||
|
get() = "alarm"
|
||||||
|
|
||||||
|
override val icon: SearchActionIcon = SearchActionIcon.Alarm
|
||||||
|
|
||||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||||
if (classifiedQuery.time != null) {
|
if (classifiedQuery.time != null) {
|
||||||
|
|||||||
@ -3,10 +3,19 @@ package de.mm20.launcher2.searchactions.builders
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import de.mm20.launcher2.searchactions.R
|
import de.mm20.launcher2.searchactions.R
|
||||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||||
import de.mm20.launcher2.searchactions.actions.TimerAction
|
|
||||||
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.TimerAction
|
||||||
|
|
||||||
object TimerActionBuilder : SearchActionBuilder {
|
class TimerActionBuilder(
|
||||||
|
override val label: String,
|
||||||
|
) : SearchActionBuilder {
|
||||||
|
constructor(context: Context) : this(context.getString(R.string.search_action_timer))
|
||||||
|
|
||||||
|
override val key: String
|
||||||
|
get() = "timer"
|
||||||
|
|
||||||
|
override val icon: SearchActionIcon = SearchActionIcon.Timer
|
||||||
|
|
||||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||||
if (classifiedQuery.timespan != null && classifiedQuery.timespan.seconds <= 86400) {
|
if (classifiedQuery.timespan != null && classifiedQuery.timespan.seconds <= 86400) {
|
||||||
|
|||||||
@ -9,13 +9,16 @@ import de.mm20.launcher2.searchactions.actions.SearchActionIcon
|
|||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
|
|
||||||
class WebsearchActionBuilder(
|
class WebsearchActionBuilder(
|
||||||
val label: String,
|
override val label: String,
|
||||||
val urlTemplate: String,
|
val urlTemplate: String,
|
||||||
val icon: SearchActionIcon = SearchActionIcon.Search,
|
override val icon: SearchActionIcon = SearchActionIcon.Search,
|
||||||
val color: Int = 0,
|
override val iconColor: Int = 0,
|
||||||
val customIcon: String? = null,
|
override val customIcon: String? = null,
|
||||||
val encoding: QueryEncoding = QueryEncoding.UrlEncode,
|
val encoding: QueryEncoding = QueryEncoding.UrlEncode,
|
||||||
) : SearchActionBuilder {
|
) : CustomizableSearchActionBuilder {
|
||||||
|
|
||||||
|
override val key: String
|
||||||
|
get() = "web://$urlTemplate"
|
||||||
|
|
||||||
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))
|
val url = urlTemplate.replace("\${1}", encodeQuery(classifiedQuery.text, encoding))
|
||||||
@ -24,7 +27,7 @@ class WebsearchActionBuilder(
|
|||||||
url = url,
|
url = url,
|
||||||
icon = icon,
|
icon = icon,
|
||||||
customIcon = customIcon,
|
customIcon = customIcon,
|
||||||
iconColor = color,
|
iconColor = iconColor,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import de.mm20.launcher2.preferences.Settings.CalculatorSearchSettings
|
|||||||
import de.mm20.launcher2.preferences.Settings.CalendarSearchSettings
|
import de.mm20.launcher2.preferences.Settings.CalendarSearchSettings
|
||||||
import de.mm20.launcher2.preferences.Settings.ContactsSearchSettings
|
import de.mm20.launcher2.preferences.Settings.ContactsSearchSettings
|
||||||
import de.mm20.launcher2.preferences.Settings.FilesSearchSettings
|
import de.mm20.launcher2.preferences.Settings.FilesSearchSettings
|
||||||
import de.mm20.launcher2.preferences.Settings.SearchActionSettings
|
|
||||||
import de.mm20.launcher2.preferences.Settings.UnitConverterSearchSettings
|
import de.mm20.launcher2.preferences.Settings.UnitConverterSearchSettings
|
||||||
import de.mm20.launcher2.preferences.Settings.WebsiteSearchSettings
|
import de.mm20.launcher2.preferences.Settings.WebsiteSearchSettings
|
||||||
import de.mm20.launcher2.preferences.Settings.WikipediaSearchSettings
|
import de.mm20.launcher2.preferences.Settings.WikipediaSearchSettings
|
||||||
@ -59,7 +58,6 @@ interface SearchService {
|
|||||||
unitConverter: UnitConverterSearchSettings,
|
unitConverter: UnitConverterSearchSettings,
|
||||||
websites: WebsiteSearchSettings,
|
websites: WebsiteSearchSettings,
|
||||||
wikipedia: WikipediaSearchSettings,
|
wikipedia: WikipediaSearchSettings,
|
||||||
searchActions: SearchActionSettings,
|
|
||||||
): Flow<ImmutableList<Searchable>>
|
): Flow<ImmutableList<Searchable>>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +85,6 @@ internal class SearchServiceImpl(
|
|||||||
unitConverter: UnitConverterSearchSettings,
|
unitConverter: UnitConverterSearchSettings,
|
||||||
websites: WebsiteSearchSettings,
|
websites: WebsiteSearchSettings,
|
||||||
wikipedia: WikipediaSearchSettings,
|
wikipedia: WikipediaSearchSettings,
|
||||||
searchActions: SearchActionSettings,
|
|
||||||
): Flow<ImmutableList<Searchable>> = channelFlow {
|
): Flow<ImmutableList<Searchable>> = channelFlow {
|
||||||
supervisorScope {
|
supervisorScope {
|
||||||
val results = MutableStateFlow(SearchResults())
|
val results = MutableStateFlow(SearchResults())
|
||||||
@ -220,7 +217,7 @@ internal class SearchServiceImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
launch {
|
launch {
|
||||||
searchActionService.search(searchActions, query)
|
searchActionService.search(query)
|
||||||
.collectLatest { r ->
|
.collectLatest { r ->
|
||||||
results.update {
|
results.update {
|
||||||
it.copy(searchActions = r)
|
it.copy(searchActions = r)
|
||||||
|
|||||||
@ -0,0 +1,54 @@
|
|||||||
|
package de.mm20.launcher2.ui.component
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Alarm
|
||||||
|
import androidx.compose.material.icons.rounded.Call
|
||||||
|
import androidx.compose.material.icons.rounded.Email
|
||||||
|
import androidx.compose.material.icons.rounded.Event
|
||||||
|
import androidx.compose.material.icons.rounded.Language
|
||||||
|
import androidx.compose.material.icons.rounded.Person
|
||||||
|
import androidx.compose.material.icons.rounded.Search
|
||||||
|
import androidx.compose.material.icons.rounded.Sms
|
||||||
|
import androidx.compose.material.icons.rounded.Timer
|
||||||
|
import androidx.compose.material.icons.rounded.Translate
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import de.mm20.launcher2.searchactions.actions.SearchActionIcon
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SearchActionIcon(
|
||||||
|
icon: SearchActionIcon,
|
||||||
|
color: Int,
|
||||||
|
customIcon: String? = null
|
||||||
|
) {
|
||||||
|
val tint = when(color) {
|
||||||
|
0 -> MaterialTheme.colorScheme.primary
|
||||||
|
1 -> Color.Unspecified
|
||||||
|
else -> Color(color)
|
||||||
|
}
|
||||||
|
if (icon != SearchActionIcon.Custom) {
|
||||||
|
Icon(
|
||||||
|
imageVector = getSearchActionIconVector(icon),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = tint,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSearchActionIconVector(icon: SearchActionIcon): ImageVector {
|
||||||
|
return when (icon) {
|
||||||
|
SearchActionIcon.Phone -> Icons.Rounded.Call
|
||||||
|
SearchActionIcon.Website -> Icons.Rounded.Language
|
||||||
|
SearchActionIcon.Alarm -> Icons.Rounded.Alarm
|
||||||
|
SearchActionIcon.Timer -> Icons.Rounded.Timer
|
||||||
|
SearchActionIcon.Contact -> Icons.Rounded.Person
|
||||||
|
SearchActionIcon.Email -> Icons.Rounded.Email
|
||||||
|
SearchActionIcon.Message -> Icons.Rounded.Sms
|
||||||
|
SearchActionIcon.Calendar -> Icons.Rounded.Event
|
||||||
|
SearchActionIcon.Translate -> Icons.Rounded.Translate
|
||||||
|
else -> Icons.Rounded.Search
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -88,7 +88,7 @@ data class LazyDragAndDropListState(
|
|||||||
fun startDrag(offset: Offset): Boolean {
|
fun startDrag(offset: Offset): Boolean {
|
||||||
val draggedItem = listState.layoutInfo.visibleItemsInfo.find {
|
val draggedItem = listState.layoutInfo.visibleItemsInfo.find {
|
||||||
Rect(
|
Rect(
|
||||||
it.offset.toOffset(),
|
(it.offset + listState.layoutInfo.beforeContentPadding).toOffset(),
|
||||||
it.size.toSize()
|
it.size.toSize()
|
||||||
).contains(offset)
|
).contains(offset)
|
||||||
} ?: return false
|
} ?: return false
|
||||||
@ -240,13 +240,16 @@ fun LazyDragAndDropRow(
|
|||||||
verticalAlignment: Alignment.Vertical = Alignment.Top,
|
verticalAlignment: Alignment.Vertical = Alignment.Top,
|
||||||
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
|
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
|
||||||
userScrollEnabled: Boolean = true,
|
userScrollEnabled: Boolean = true,
|
||||||
|
bidirectionalDrag: Boolean = true,
|
||||||
content: LazyListScope.() -> Unit
|
content: LazyListScope.() -> Unit
|
||||||
) {
|
) {
|
||||||
LazyRow(
|
LazyRow(
|
||||||
modifier = modifier.dragAndDrop(
|
modifier = modifier.dragAndDrop(
|
||||||
state,
|
state,
|
||||||
LocalLayoutDirection.current == LayoutDirection.Rtl,
|
isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl,
|
||||||
LocalHapticFeedback.current
|
hapticFeedback = LocalHapticFeedback.current,
|
||||||
|
dragVertical = bidirectionalDrag,
|
||||||
|
dragHorizontal = true,
|
||||||
),
|
),
|
||||||
state = state.listState,
|
state = state.listState,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
@ -269,13 +272,16 @@ fun LazyDragAndDropColumn(
|
|||||||
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
||||||
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
|
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
|
||||||
userScrollEnabled: Boolean = true,
|
userScrollEnabled: Boolean = true,
|
||||||
|
bidirectionalDrag: Boolean = true,
|
||||||
content: LazyListScope.() -> Unit
|
content: LazyListScope.() -> Unit
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = modifier.dragAndDrop(
|
modifier = modifier.dragAndDrop(
|
||||||
state,
|
state,
|
||||||
LocalLayoutDirection.current == LayoutDirection.Rtl,
|
isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl,
|
||||||
LocalHapticFeedback.current
|
hapticFeedback = LocalHapticFeedback.current,
|
||||||
|
dragVertical = true,
|
||||||
|
dragHorizontal = bidirectionalDrag,
|
||||||
),
|
),
|
||||||
state = state.listState,
|
state = state.listState,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
@ -291,6 +297,8 @@ fun LazyDragAndDropColumn(
|
|||||||
fun Modifier.dragAndDrop(
|
fun Modifier.dragAndDrop(
|
||||||
state: LazyDragAndDropListState,
|
state: LazyDragAndDropListState,
|
||||||
isRtl: Boolean,
|
isRtl: Boolean,
|
||||||
|
dragVertical: Boolean = true,
|
||||||
|
dragHorizontal: Boolean = true,
|
||||||
hapticFeedback: HapticFeedback
|
hapticFeedback: HapticFeedback
|
||||||
) =
|
) =
|
||||||
this then pointerInput(null) {
|
this then pointerInput(null) {
|
||||||
@ -302,9 +310,18 @@ fun Modifier.dragAndDrop(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDrag = { _, dragAmount ->
|
onDrag = { _, dragAmount ->
|
||||||
scope.launch { state.drag(dragAmount.let {
|
scope.launch {
|
||||||
if (isRtl) it.copy(x = -it.x) else it
|
state.drag(
|
||||||
}) }
|
when {
|
||||||
|
!dragVertical && !dragHorizontal -> Offset.Zero
|
||||||
|
dragVertical && !dragHorizontal -> Offset(0f, dragAmount.y)
|
||||||
|
!dragVertical && dragHorizontal && isRtl -> Offset(-dragAmount.x, 0f)
|
||||||
|
!dragVertical && dragHorizontal && !isRtl -> Offset(dragAmount.x, 0f)
|
||||||
|
dragVertical && dragHorizontal && isRtl -> Offset(-dragAmount.x, dragAmount.y)
|
||||||
|
else -> dragAmount
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onDragCancel = {
|
onDragCancel = {
|
||||||
scope.launch { state.cancelDrag() }
|
scope.launch { state.cancelDrag() }
|
||||||
|
|||||||
@ -95,7 +95,6 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
shortcuts = it.appShortcutSearch,
|
shortcuts = it.appShortcutSearch,
|
||||||
websites = it.websiteSearch,
|
websites = it.websiteSearch,
|
||||||
wikipedia = it.wikipediaSearch,
|
wikipedia = it.wikipediaSearch,
|
||||||
searchActions = it.searchActions,
|
|
||||||
).collectLatest { results ->
|
).collectLatest { results ->
|
||||||
hiddenItemKeys.collectLatest { hiddenKeys ->
|
hiddenItemKeys.collectLatest { hiddenKeys ->
|
||||||
val hidden = mutableListOf<SavableSearchable>()
|
val hidden = mutableListOf<SavableSearchable>()
|
||||||
|
|||||||
@ -6,17 +6,6 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.rounded.Alarm
|
|
||||||
import androidx.compose.material.icons.rounded.Call
|
|
||||||
import androidx.compose.material.icons.rounded.Email
|
|
||||||
import androidx.compose.material.icons.rounded.Event
|
|
||||||
import androidx.compose.material.icons.rounded.Language
|
|
||||||
import androidx.compose.material.icons.rounded.Person
|
|
||||||
import androidx.compose.material.icons.rounded.Search
|
|
||||||
import androidx.compose.material.icons.rounded.Sms
|
|
||||||
import androidx.compose.material.icons.rounded.Timer
|
|
||||||
import androidx.compose.material.icons.rounded.Translate
|
|
||||||
import androidx.compose.material3.AssistChip
|
import androidx.compose.material3.AssistChip
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -28,7 +17,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
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.ui.component.SearchActionIcon
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchBarActions(
|
fun SearchBarActions(
|
||||||
@ -53,27 +42,7 @@ fun SearchBarActions(
|
|||||||
},
|
},
|
||||||
label = { Text(it.label) },
|
label = { Text(it.label) },
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
val icon = it.icon
|
SearchActionIcon(icon = it.icon, color = it.iconColor, customIcon = it.customIcon)
|
||||||
if (it.icon != SearchActionIcon.Custom) {
|
|
||||||
Icon(
|
|
||||||
imageVector = when (it.icon) {
|
|
||||||
SearchActionIcon.Phone -> Icons.Rounded.Call
|
|
||||||
SearchActionIcon.Website -> Icons.Rounded.Language
|
|
||||||
SearchActionIcon.Alarm -> Icons.Rounded.Alarm
|
|
||||||
SearchActionIcon.Timer -> Icons.Rounded.Timer
|
|
||||||
SearchActionIcon.Contact -> Icons.Rounded.Person
|
|
||||||
SearchActionIcon.Email -> Icons.Rounded.Email
|
|
||||||
SearchActionIcon.Message -> Icons.Rounded.Sms
|
|
||||||
SearchActionIcon.Calendar -> Icons.Rounded.Event
|
|
||||||
SearchActionIcon.Translate -> Icons.Rounded.Translate
|
|
||||||
else -> Icons.Rounded.Search
|
|
||||||
},
|
|
||||||
contentDescription = null,
|
|
||||||
tint = if (it.iconColor == 0) MaterialTheme.colorScheme.primary else Color(
|
|
||||||
it.iconColor
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/*leadingIcon = {
|
/*leadingIcon = {
|
||||||
val icon = it.icon
|
val icon = it.icon
|
||||||
|
|||||||
@ -1,115 +1,166 @@
|
|||||||
package de.mm20.launcher2.ui.settings.searchactions
|
package de.mm20.launcher2.ui.settings.searchactions
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Alarm
|
import androidx.compose.material.icons.rounded.Add
|
||||||
import androidx.compose.material.icons.rounded.CalendarToday
|
import androidx.compose.material.icons.rounded.ArrowBack
|
||||||
import androidx.compose.material.icons.rounded.Call
|
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||||
import androidx.compose.material.icons.rounded.Email
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material.icons.rounded.Event
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material.icons.rounded.Language
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material.icons.rounded.Person
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material.icons.rounded.Sms
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material.icons.rounded.Timer
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.zIndex
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import de.mm20.launcher2.preferences.Settings
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
|
import de.mm20.launcher2.searchactions.builders.CustomizableSearchActionBuilder
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
import de.mm20.launcher2.ui.component.SearchActionIcon
|
||||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
import de.mm20.launcher2.ui.component.getSearchActionIconVector
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||||
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
||||||
|
import de.mm20.launcher2.ui.launcher.helper.DraggableItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.helper.LazyDragAndDropColumn
|
||||||
|
import de.mm20.launcher2.ui.launcher.helper.rememberLazyDragAndDropListState
|
||||||
|
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchActionsSettingsScreen() {
|
fun SearchActionsSettingsScreen() {
|
||||||
val viewModel: SearchActionsSettingsScreenVM = viewModel()
|
val viewModel: SearchActionsSettingsScreenVM = viewModel()
|
||||||
val settings by viewModel.searchActionSettings.observeAsState(
|
val navController = LocalNavController.current
|
||||||
Settings.SearchActionSettings.getDefaultInstance()
|
val systemUiController = rememberSystemUiController()
|
||||||
|
systemUiController.setStatusBarColor(MaterialTheme.colorScheme.surface)
|
||||||
|
systemUiController.setNavigationBarColor(Color.Black)
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
|
|
||||||
|
val activity = LocalContext.current as? AppCompatActivity
|
||||||
|
|
||||||
|
val listState = rememberLazyDragAndDropListState(
|
||||||
|
onDragStart = {
|
||||||
|
it.key != "divider" && !(it.key as String).startsWith("disabled-")
|
||||||
|
},
|
||||||
|
onItemMove = { from, to -> viewModel.moveItem(from.index, to.index) }
|
||||||
)
|
)
|
||||||
|
|
||||||
PreferenceScreen(stringResource(id = R.string.preference_screen_search_actions)) {
|
val searchActions by viewModel.searchActions.observeAsState(emptyList())
|
||||||
item {
|
val disabledActions by viewModel.disabledActions.observeAsState(emptyList())
|
||||||
PreferenceCategory {
|
|
||||||
SwitchPreference(
|
Scaffold(
|
||||||
icon = Icons.Rounded.Call,
|
floatingActionButton = {
|
||||||
title = stringResource(R.string.search_action_call),
|
FloatingActionButton(onClick = { /*TODO*/ }) {
|
||||||
value = settings.call,
|
Icon(imageVector = Icons.Rounded.Add, contentDescription = null)
|
||||||
onValueChanged = {
|
}
|
||||||
viewModel.updateSettings {
|
},
|
||||||
setCall(it)
|
topBar = {
|
||||||
|
CenterAlignedTopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(id = R.string.preference_screen_search_actions),
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
maxLines = 1
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
if (navController?.navigateUp() != true) {
|
||||||
|
activity?.onBackPressed()
|
||||||
}
|
}
|
||||||
},
|
}) {
|
||||||
|
Icon(imageVector = Icons.Rounded.ArrowBack, contentDescription = "Back")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
|
||||||
|
LazyDragAndDropColumn(
|
||||||
|
state = listState,
|
||||||
|
bidirectionalDrag = false,
|
||||||
|
contentPadding = it,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
items = searchActions,
|
||||||
|
key = { it.key }
|
||||||
|
) { item ->
|
||||||
|
DraggableItem(
|
||||||
|
state = listState,
|
||||||
|
key = item.key
|
||||||
|
) {
|
||||||
|
val elevation by animateDpAsState(if (it) 4.dp else 0.dp)
|
||||||
|
Surface(
|
||||||
|
shadowElevation = elevation,
|
||||||
|
tonalElevation = elevation,
|
||||||
|
modifier = Modifier.zIndex(if (it) 1f else 0f)
|
||||||
|
) {
|
||||||
|
if (item is CustomizableSearchActionBuilder) {
|
||||||
|
Preference(
|
||||||
|
icon = {
|
||||||
|
SearchActionIcon(
|
||||||
|
icon = item.icon,
|
||||||
|
color = item.iconColor,
|
||||||
|
customIcon = item.customIcon
|
||||||
|
)
|
||||||
|
},
|
||||||
|
title = item.label
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
SwitchPreference(
|
||||||
|
icon = getSearchActionIconVector(item.icon),
|
||||||
|
title = item.label,
|
||||||
|
value = true,
|
||||||
|
onValueChanged = {
|
||||||
|
viewModel.removeAction(item)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item(key = "divider") {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(0.5.dp)
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
items(
|
||||||
|
items = disabledActions,
|
||||||
|
key = { "disabled-${it.key}" }
|
||||||
|
) { item ->
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
icon = Icons.Rounded.Sms,
|
icon = getSearchActionIconVector(item.icon),
|
||||||
title = stringResource(R.string.search_action_message),
|
title = item.label,
|
||||||
value = settings.message,
|
value = false,
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
viewModel.updateSettings {
|
viewModel.addAction(item)
|
||||||
setMessage(it)
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
SwitchPreference(
|
|
||||||
icon = Icons.Rounded.Email,
|
|
||||||
title = stringResource(R.string.search_action_email),
|
|
||||||
value = settings.email,
|
|
||||||
onValueChanged = {
|
|
||||||
viewModel.updateSettings {
|
|
||||||
setEmail(it)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
SwitchPreference(
|
|
||||||
icon = Icons.Rounded.Person,
|
|
||||||
title = stringResource(R.string.search_action_contact),
|
|
||||||
value = settings.contact,
|
|
||||||
onValueChanged = {
|
|
||||||
viewModel.updateSettings {
|
|
||||||
setContact(it)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
SwitchPreference(
|
|
||||||
icon = Icons.Rounded.Alarm,
|
|
||||||
title = stringResource(R.string.search_action_alarm),
|
|
||||||
value = settings.setAlarm,
|
|
||||||
onValueChanged = {
|
|
||||||
viewModel.updateSettings {
|
|
||||||
setSetAlarm(it)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
SwitchPreference(
|
|
||||||
icon = Icons.Rounded.Timer,
|
|
||||||
title = stringResource(R.string.search_action_timer),
|
|
||||||
value = settings.startTimer,
|
|
||||||
onValueChanged = {
|
|
||||||
viewModel.updateSettings {
|
|
||||||
setStartTimer(it)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
SwitchPreference(
|
|
||||||
icon = Icons.Rounded.Event,
|
|
||||||
title = stringResource(R.string.search_action_event),
|
|
||||||
value = settings.scheduleEvent,
|
|
||||||
onValueChanged = {
|
|
||||||
viewModel.updateSettings {
|
|
||||||
setScheduleEvent(it)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
SwitchPreference(
|
|
||||||
icon = Icons.Rounded.Language,
|
|
||||||
title = stringResource(R.string.search_action_open_url),
|
|
||||||
value = settings.openUrl,
|
|
||||||
onValueChanged = {
|
|
||||||
viewModel.updateSettings {
|
|
||||||
setOpenUrl(it)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,27 +2,39 @@ package de.mm20.launcher2.ui.settings.searchactions
|
|||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import de.mm20.launcher2.searchactions.SearchActionService
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.searchactions.builders.SearchActionBuilder
|
||||||
import de.mm20.launcher2.preferences.Settings.SearchActionSettings
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
|
|
||||||
class SearchActionsSettingsScreenVM : ViewModel(), KoinComponent {
|
class SearchActionsSettingsScreenVM : ViewModel(), KoinComponent {
|
||||||
private val dataStore: LauncherDataStore by inject()
|
private val searchActionService: SearchActionService by inject()
|
||||||
|
|
||||||
val searchActionSettings = dataStore.data.map { it.searchActions }.asLiveData()
|
val searchActions = searchActionService
|
||||||
|
.getSearchActionBuilders()
|
||||||
|
.asLiveData()
|
||||||
|
|
||||||
fun updateSettings(block: SearchActionSettings.Builder.() -> SearchActionSettings.Builder) {
|
val disabledActions = searchActionService
|
||||||
viewModelScope.launch {
|
.getDisabledActionBuilders()
|
||||||
dataStore.updateData {
|
.asLiveData()
|
||||||
it.toBuilder()
|
|
||||||
.setSearchActions(
|
fun addAction(searchAction: SearchActionBuilder) {
|
||||||
it.searchActions.toBuilder().block()
|
val actions =
|
||||||
).build()
|
searchActions.value?.filter { it.key != searchAction.key }?.plus(searchAction) ?: return
|
||||||
}
|
searchActionService.saveSearchActionBuilders(actions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeAction(searchAction: SearchActionBuilder) {
|
||||||
|
val actions = searchActions.value?.filter { it.key != searchAction.key } ?: return
|
||||||
|
searchActionService.saveSearchActionBuilders(actions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun moveItem(fromIndex: Int, toIndex: Int) {
|
||||||
|
val actions = searchActions.value?.toMutableList() ?: return
|
||||||
|
if (fromIndex > actions.lastIndex) return
|
||||||
|
if (toIndex > actions.lastIndex) return
|
||||||
|
val item = actions.removeAt(fromIndex)
|
||||||
|
actions.add(toIndex, item)
|
||||||
|
searchActionService.saveSearchActionBuilders(actions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user