Refactor favorites and searchable database
This commit is contained in:
@@ -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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user