Refactor favorites and searchable database

This commit is contained in:
MM20
2023-04-10 22:44:05 +02:00
parent 582c94b6af
commit ed70097c38
49 changed files with 1238 additions and 501 deletions
@@ -0,0 +1,496 @@
{
"formatVersion": 1,
"database": {
"version": 24,
"identityHash": "fd75b8c610f9b92eeb42a3ba1474914d",
"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, `type` TEXT NOT NULL, `searchable` TEXT NOT NULL, `launchCount` INTEGER NOT NULL DEFAULT 0, `pinPosition` INTEGER NOT NULL DEFAULT 0, `hidden` INTEGER NOT NULL DEFAULT 0, `weight` REAL NOT NULL DEFAULT 0.0, PRIMARY KEY(`key`))",
"fields": [
{
"fieldPath": "key",
"columnName": "key",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "serializedSearchable",
"columnName": "searchable",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "launchCount",
"columnName": "launchCount",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "pinPosition",
"columnName": "pinPosition",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "hidden",
"columnName": "hidden",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "weight",
"columnName": "weight",
"affinity": "REAL",
"notNull": true,
"defaultValue": "0.0"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"key"
]
},
"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, `packageName` TEXT, `activityName` TEXT, `drawable` TEXT, `extras` TEXT, `iconPack` TEXT NOT NULL, `name` TEXT, `themed` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
"fields": [
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "packageName",
"columnName": "packageName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityName",
"columnName": "activityName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "drawable",
"columnName": "drawable",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "extras",
"columnName": "extras",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "iconPack",
"columnName": "iconPack",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "themed",
"columnName": "themed",
"affinity": "INTEGER",
"notNull": true
},
{
"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, `themed` INTEGER 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
},
{
"fieldPath": "themed",
"columnName": "themed",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"packageName"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "Widget",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `config` TEXT, `position` INTEGER NOT NULL, `id` BLOB NOT NULL, `parentId` BLOB, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "config",
"columnName": "config",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "position",
"columnName": "position",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "BLOB",
"notNull": true
},
{
"fieldPath": "parentId",
"columnName": "parentId",
"affinity": "BLOB",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"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": []
},
{
"tableName": "SearchAction",
"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": [
{
"fieldPath": "position",
"columnName": "position",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "data",
"columnName": "data",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "customIcon",
"columnName": "customIcon",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "options",
"columnName": "options",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"position"
]
},
"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, 'fd75b8c610f9b92eeb42a3ba1474914d')"
]
}
}
@@ -22,6 +22,7 @@ import de.mm20.launcher2.database.migrations.Migration_19_20
import de.mm20.launcher2.database.migrations.Migration_20_21
import de.mm20.launcher2.database.migrations.Migration_21_22
import de.mm20.launcher2.database.migrations.Migration_22_23
import de.mm20.launcher2.database.migrations.Migration_23_24
import de.mm20.launcher2.database.migrations.Migration_6_7
import de.mm20.launcher2.database.migrations.Migration_7_8
import de.mm20.launcher2.database.migrations.Migration_8_9
@@ -39,14 +40,15 @@ import java.util.UUID
WidgetEntity::class,
CustomAttributeEntity::class,
SearchActionEntity::class
], version = 23, exportSchema = true
], version = 24, exportSchema = true
)
@TypeConverters(ComponentNameConverter::class, StringListConverter::class)
@TypeConverters(ComponentNameConverter::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun weatherDao(): WeatherDao
abstract fun searchDao(): SearchDao
abstract fun iconDao(): IconDao
abstract fun searchableDao(): SearchableDao
abstract fun widgetDao(): WidgetDao
abstract fun currencyDao(): CurrencyDao
abstract fun backupDao(): BackupRestoreDao
@@ -114,6 +116,7 @@ abstract class AppDatabase : RoomDatabase() {
Migration_20_21(),
Migration_21_22(),
Migration_22_23(),
Migration_23_24(),
).build()
if (_instance == null) _instance = instance
return instance
@@ -1,164 +0,0 @@
package de.mm20.launcher2.database
import androidx.room.*
import de.mm20.launcher2.database.entities.SavedSearchableEntity
import kotlinx.coroutines.flow.Flow
@Dao
interface SearchDao {
@Insert
fun insertAll(items: List<SavedSearchableEntity>)
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insertAllSkipExisting(items: List<SavedSearchableEntity>)
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insertSkipExisting(items: SavedSearchableEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAllReplaceExisting(items: List<SavedSearchableEntity>)
@Query(
"SELECT * FROM Searchable " +
"WHERE ((:manuallySorted AND pinned > 1) OR " +
"(:automaticallySorted AND pinned = 1) OR" +
"(:frequentlyUsed AND pinned = 0 AND launchCount > 0)" +
") AND hidden = 0 ORDER BY pinned DESC, weight DESC, launchCount DESC LIMIT :limit"
)
fun getFavorites(
manuallySorted: Boolean = false,
automaticallySorted: Boolean = false,
frequentlyUsed: Boolean = false,
limit: Int,
): Flow<List<SavedSearchableEntity>>
@Query(
"SELECT * FROM Searchable " +
"WHERE `type` IN (:includeTypes) AND (" +
"(:manuallySorted AND pinned > 1) OR " +
"(:automaticallySorted AND pinned = 1) OR" +
"(:frequentlyUsed AND pinned = 0 AND launchCount > 0)" +
") AND hidden = 0 ORDER BY pinned DESC, weight DESC, launchCount DESC LIMIT :limit"
)
fun getFavoritesWithTypes(
includeTypes: List<String>,
manuallySorted: Boolean = false,
automaticallySorted: Boolean = false,
frequentlyUsed: Boolean = false,
limit: Int,
): Flow<List<SavedSearchableEntity>>
@Query(
"SELECT * FROM Searchable " +
"WHERE `type` NOT IN (:excludeTypes) AND (" +
"(:manuallySorted AND pinned > 1) OR " +
"(:automaticallySorted AND pinned = 1) OR" +
"(:frequentlyUsed AND pinned = 0 AND launchCount > 0)" +
") AND hidden = 0 ORDER BY pinned DESC, weight DESC, launchCount DESC LIMIT :limit"
)
fun getFavoritesWithoutTypes(
excludeTypes: List<String>,
manuallySorted: Boolean = false,
automaticallySorted: Boolean = false,
frequentlyUsed: Boolean = false,
limit: Int,
): Flow<List<SavedSearchableEntity>>
@Query("SELECT `key` FROM Searchable WHERE hidden = 1 AND type = 'calendar'")
fun getHiddenCalendarEventKeys(): Flow<List<String>>
@Query("DELETE FROM Searchable WHERE `key` IN (:keys)")
fun deleteAll(keys: List<String>)
@Query("UPDATE Searchable SET pinned = 1, hidden = 0 WHERE `key` = :key")
fun pinExistingItem(key: String)
@Transaction
fun pinToFavorites(item: SavedSearchableEntity) {
pinExistingItem(item.key)
insertSkipExisting(item)
}
@Query("UPDATE Searchable SET pinned = 0 WHERE `key` = :key")
fun unpinFavorite(key: String)
@Query("DELETE FROM Searchable WHERE `key` = :key")
suspend fun deleteByKey(key: String)
@Query("UPDATE Searchable SET pinned = 0 WHERE `key` = :key")
fun unpinApp(key: String)
@Query("SELECT pinned FROM Searchable WHERE `key` = :key UNION SELECT 0 as pinned ORDER BY pinned DESC LIMIT 1")
fun isPinned(key: String): Flow<Boolean>
@Query("UPDATE Searchable SET hidden = 1, pinned = 0 WHERE `key` = :key")
fun hideExistingItem(key: String)
@Transaction
fun hideItem(item: SavedSearchableEntity) {
hideExistingItem(item.key)
insertSkipExisting(item)
}
@Query("UPDATE Searchable SET hidden = 0 WHERE `key` = :key")
fun unhideItem(key: String)
@Query("SELECT hidden FROM Searchable WHERE `key` = :key UNION SELECT 0 as hidden ORDER BY hidden DESC LIMIT 1")
fun isHidden(key: String): Flow<Boolean>
@Query("SELECT `key` FROM SEARCHABLE WHERE hidden = 1")
fun getHiddenItemKeys(): Flow<List<String>>
@Query("SELECT * FROM SEARCHABLE WHERE hidden = 1")
fun getHiddenItems(): Flow<List<SavedSearchableEntity>>
@Query("UPDATE Searchable SET launchCount = launchCount + 1 WHERE `key` = :key")
fun incrementExistingLaunchCount(key: String)
@Transaction
fun incrementLaunchCount(item: SavedSearchableEntity, alpha: Double) {
incrementExistingLaunchCount(item.key)
increaseWeightWhere(item.key, alpha)
reduceWeightExcept(item.key, alpha)
insertSkipExisting(item)
}
@Query("SELECT * FROM Searchable WHERE `key` = :key")
fun getFavorite(key: String): SavedSearchableEntity?
@Query("SELECT * FROM Searchable WHERE `key` IN (:keys)")
suspend fun getFromKeys(keys: List<String>): List<SavedSearchableEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertReplaceExisting(toDatabaseEntity: SavedSearchableEntity)
@Transaction
fun saveFavorites(favorites: List<SavedSearchableEntity>) {
deleteAllFavorites()
insertAll(favorites)
}
@Query("DELETE FROM Searchable WHERE hidden = 0")
fun deleteAllFavorites()
@Query("UPDATE Searchable SET `pinned` = 0")
fun unpinAll()
@Query("UPDATE Searchable SET `pinned` = 0, `launchCount` = 0 WHERE `key` = :key")
suspend fun resetPinStatusAndLaunchCounter(key: String)
@Query("SELECT `key` FROM Searchable WHERE `key` IN (:keys) AND launchCount > 0 ORDER BY launchCount DESC, pinned DESC")
fun sortByRelevance(keys: List<String>): Flow<List<String>>
@Query("SELECT `key` FROM Searchable WHERE `key` IN (:keys) ORDER BY `weight` DESC, pinned DESC")
fun sortByWeight(keys: List<String>): Flow<List<String>>
@Query("UPDATE Searchable SET `weight` = `weight` * (1.0 - :alpha) WHERE `key` != :key")
fun reduceWeightExcept(key: String, alpha: Double)
@Query("UPDATE Searchable SET `weight` = `weight` + :alpha * (1.0 - `weight`) WHERE `key` == :key")
fun increaseWeightWhere(key: String, alpha: Double)
}
@@ -0,0 +1,178 @@
package de.mm20.launcher2.database
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import androidx.room.Upsert
import de.mm20.launcher2.database.entities.SavedSearchableEntity
import de.mm20.launcher2.database.entities.SavedSearchableUpdatePinEntity
import kotlinx.coroutines.flow.Flow
@Dao
interface SearchableDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(searchable: SavedSearchableEntity)
@Upsert(entity = SavedSearchableEntity::class)
suspend fun upsert(searchable: SavedSearchableEntity)
@Upsert(entity = SavedSearchableEntity::class)
suspend fun upsert(searchable: List<SavedSearchableUpdatePinEntity>)
@Update(entity = SavedSearchableEntity::class)
suspend fun update(searchable: SavedSearchableUpdatePinEntity)
@Query(
"SELECT * FROM Searchable " +
"WHERE (" +
"(:manuallySorted AND pinPosition > 1) OR " +
"(:automaticallySorted AND pinPosition = 1) OR" +
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
"(:hidden AND hidden = 1)" +
") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
)
fun get(
manuallySorted: Boolean = false,
automaticallySorted: Boolean = false,
frequentlyUsed: Boolean = false,
hidden: Boolean = false,
limit: Int,
): Flow<List<SavedSearchableEntity>>
@Query(
"SELECT * FROM Searchable " +
"WHERE (`type` IN (:includeTypes)) AND " +
"(" +
"(:manuallySorted AND pinPosition > 1) OR " +
"(:automaticallySorted AND pinPosition = 1) OR" +
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
"(:hidden AND hidden = 1)" +
") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
)
fun getIncludeTypes(
includeTypes: List<String>?,
manuallySorted: Boolean = false,
automaticallySorted: Boolean = false,
frequentlyUsed: Boolean = false,
hidden: Boolean = false,
limit: Int,
): Flow<List<SavedSearchableEntity>>
@Query(
"SELECT * FROM Searchable " +
"WHERE (`type` NOT IN (:excludeTypes)) AND " +
"(" +
"(:manuallySorted AND pinPosition > 1) OR " +
"(:automaticallySorted AND pinPosition = 1) OR" +
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
"(:hidden AND hidden = 1)" +
") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
)
fun getExcludeTypes(
excludeTypes: List<String>?,
manuallySorted: Boolean = false,
automaticallySorted: Boolean = false,
frequentlyUsed: Boolean = false,
hidden: Boolean = false,
limit: Int,
): Flow<List<SavedSearchableEntity>>
@Query(
"SELECT `key` FROM Searchable " +
"WHERE (" +
"(:manuallySorted AND pinPosition > 1) OR " +
"(:automaticallySorted AND pinPosition = 1) OR" +
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
"(:hidden AND hidden = 1)" +
") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
)
fun getKeys(
manuallySorted: Boolean = false,
automaticallySorted: Boolean = false,
frequentlyUsed: Boolean = false,
hidden: Boolean = false,
limit: Int,
): Flow<List<String>>
@Query(
"SELECT `key` FROM Searchable " +
"WHERE (`type` IN (:includeTypes)) AND " +
"(" +
"(:manuallySorted AND pinPosition > 1) OR " +
"(:automaticallySorted AND pinPosition = 1) OR" +
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
"(:hidden AND hidden = 1)" +
") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
)
fun getKeysIncludeTypes(
includeTypes: List<String>?,
manuallySorted: Boolean = false,
automaticallySorted: Boolean = false,
frequentlyUsed: Boolean = false,
hidden: Boolean = false,
limit: Int,
): Flow<List<String>>
@Query(
"SELECT `key` FROM Searchable " +
"WHERE (`type` NOT IN (:excludeTypes)) AND " +
"(" +
"(:manuallySorted AND pinPosition > 1) OR " +
"(:automaticallySorted AND pinPosition = 1) OR" +
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
"(:hidden AND hidden = 1)" +
") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
)
fun getKeysExcludeTypes(
excludeTypes: List<String>?,
manuallySorted: Boolean = false,
automaticallySorted: Boolean = false,
frequentlyUsed: Boolean = false,
hidden: Boolean = false,
limit: Int,
): Flow<List<String>>
@Query("SELECT * FROM Searchable WHERE `key` IN (:keys)")
suspend fun getByKeys(keys: List<String>): List<SavedSearchableEntity>
@Query("SELECT * FROM Searchable WHERE `key` = :key")
fun getByKey(key: String): Flow<SavedSearchableEntity?>
@Transaction
suspend fun touch(item: SavedSearchableEntity, alpha: Double) {
incrementLaunchCount(item.key)
increaseWeightWhere(item.key, alpha)
reduceWeightExcept(item.key, alpha)
insert(item)
}
@Query("UPDATE Searchable SET launchCount = launchCount + 1 WHERE `key` = :key")
fun incrementLaunchCount(key: String)
@Query("UPDATE Searchable SET `weight` = `weight` * (1.0 - :alpha) WHERE `key` != :key")
fun reduceWeightExcept(key: String, alpha: Double)
@Query("UPDATE Searchable SET `weight` = `weight` + :alpha * (1.0 - `weight`) WHERE `key` == :key")
fun increaseWeightWhere(key: String, alpha: Double)
@Query("DELETE FROM Searchable WHERE `key` = :key")
suspend fun delete(key: String)
@Query("UPDATE Searchable SET `pinPosition` = 0")
suspend fun unpinAll()
@Query("SELECT `key` FROM Searchable WHERE `key` IN (:keys) AND launchCount > 0 ORDER BY launchCount DESC, pinPosition DESC")
fun sortByRelevance(keys: List<String>): Flow<List<String>>
@Query("SELECT `key` FROM Searchable WHERE `key` IN (:keys) ORDER BY `weight` DESC, pinPosition DESC")
fun sortByWeight(keys: List<String>): Flow<List<String>>
@Query("SELECT hidden FROM Searchable WHERE `key` = :key UNION SELECT 0 as hidden ORDER BY hidden DESC LIMIT 1")
fun isHidden(key: String): Flow<Boolean>
@Query("SELECT pinPosition FROM Searchable WHERE `key` = :key UNION SELECT 0 as pinPosition ORDER BY pinPosition DESC LIMIT 1")
fun isPinned(key: String): Flow<Boolean>
}
@@ -9,8 +9,15 @@ data class SavedSearchableEntity(
@PrimaryKey val key: String,
val type: String,
@ColumnInfo(name = "searchable") val serializedSearchable: String,
var launchCount: Int,
@ColumnInfo(name = "pinned") var pinPosition: Int,
var hidden: Boolean,
@ColumnInfo(defaultValue = "0.0") var weight: Double
@ColumnInfo(defaultValue = "0") val launchCount: Int,
@ColumnInfo(defaultValue = "0") val pinPosition: Int,
@ColumnInfo(defaultValue = "0") val hidden: Boolean,
@ColumnInfo(defaultValue = "0.0") val weight: Double
)
data class SavedSearchableUpdatePinEntity(
val key: String,
val type: String,
@ColumnInfo(name = "searchable") val serializedSearchable: String,
val pinPosition: Int? = null,
)
@@ -0,0 +1,31 @@
package de.mm20.launcher2.database.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration_23_24 : Migration(23, 24) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Searchable RENAME TO Searchable_old")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Searchable` (
`key` TEXT NOT NULL,
`type` TEXT NOT NULL,
`searchable` TEXT NOT NULL,
`launchCount` INTEGER NOT NULL DEFAULT 0,
`pinPosition` INTEGER NOT NULL DEFAULT 0,
`hidden` INTEGER NOT NULL DEFAULT 0,
`weight` DOUBLE NOT NULL DEFAULT 0.0,
PRIMARY KEY(`key`)
)
"""
)
database.execSQL(
"""
INSERT INTO `Searchable` (`key`, `type`, `searchable`, `launchCount`, `pinPosition`, `hidden`, `weight`)
SELECT `key`, `type`, `searchable`, `launchCount`, `pinned`, `hidden`, `weight` FROM `Searchable_old`
"""
)
database.execSQL("DROP TABLE Searchable_old")
}
}