Search: introduce weighted reordering (#272)
* Database: backend for weights & timestamps - Migration 21->22 - SearchableLaunchTimestampEntity.kt - `weight`-column in SavedSearchableEntity - Queries for sorting by and adjusting weight - Queries for sorting by recent use * move weightfactor access to favoritesRepository * reorder calls in SearchVM * no more datahoarding * add settings screen for ordering * ui fix, animations * move ordering settings out of own screen * remove unused localization * weight factors * larger factor for WeightFactor.High * cleanup * sort favorites by weight * icons for favorite screen * I hate coming up with descriptive strings * add default weight factor to settings migration * Add default values for search result ordering preferences * Favorites settings: change order of preferences * Change strings * Replace favorites variability slider with list preference * migration initial weight * Change labels * Include searchable weight column in backup --------- Co-authored-by: MM20 <15646950+MM2-0@users.noreply.github.com>
This commit is contained in:
@@ -7,7 +7,6 @@ import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import de.mm20.launcher2.database.entities.*
|
||||
import de.mm20.launcher2.database.migrations.Migration_10_11
|
||||
@@ -21,6 +20,7 @@ import de.mm20.launcher2.database.migrations.Migration_17_18
|
||||
import de.mm20.launcher2.database.migrations.Migration_18_19
|
||||
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_6_7
|
||||
import de.mm20.launcher2.database.migrations.Migration_7_8
|
||||
import de.mm20.launcher2.database.migrations.Migration_8_9
|
||||
@@ -35,8 +35,8 @@ import de.mm20.launcher2.database.migrations.Migration_9_10
|
||||
IconPackEntity::class,
|
||||
WidgetEntity::class,
|
||||
CustomAttributeEntity::class,
|
||||
SearchActionEntity::class,
|
||||
], version = 21, exportSchema = true
|
||||
SearchActionEntity::class
|
||||
], version = 22, exportSchema = true
|
||||
)
|
||||
@TypeConverters(ComponentNameConverter::class, StringListConverter::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
@@ -104,6 +104,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration_18_19(),
|
||||
Migration_19_20(),
|
||||
Migration_20_21(),
|
||||
Migration_21_22()
|
||||
).build()
|
||||
if (_instance == null) _instance = instance
|
||||
return instance
|
||||
|
||||
@@ -7,7 +7,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
@Dao
|
||||
interface SearchDao {
|
||||
|
||||
@Insert()
|
||||
@Insert
|
||||
fun insertAll(items: List<SavedSearchableEntity>)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
@@ -19,12 +19,13 @@ interface SearchDao {
|
||||
@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)" +
|
||||
") ORDER BY pinned DESC, launchCount DESC LIMIT :limit")
|
||||
@Query(
|
||||
"SELECT * FROM Searchable " +
|
||||
"WHERE ((:manuallySorted AND pinned > 1) OR " +
|
||||
"(:automaticallySorted AND pinned = 1) OR" +
|
||||
"(:frequentlyUsed AND pinned = 0 AND launchCount > 0)" +
|
||||
") ORDER BY pinned DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||
)
|
||||
fun getFavorites(
|
||||
manuallySorted: Boolean = false,
|
||||
automaticallySorted: Boolean = false,
|
||||
@@ -32,12 +33,14 @@ interface SearchDao {
|
||||
limit: Int,
|
||||
): Flow<List<SavedSearchableEntity>>
|
||||
|
||||
@Query("SELECT * FROM Searchable " +
|
||||
"WHERE SUBSTR(`key`, 0, INSTR(`key`, '://')) IN (:includeTypes) AND (" +
|
||||
"(:manuallySorted AND pinned > 1) OR " +
|
||||
"(:automaticallySorted AND pinned = 1) OR" +
|
||||
"(:frequentlyUsed AND pinned = 0 AND launchCount > 0)" +
|
||||
") ORDER BY pinned DESC, launchCount DESC LIMIT :limit")
|
||||
@Query(
|
||||
"SELECT * FROM Searchable " +
|
||||
"WHERE SUBSTR(`key`, 0, INSTR(`key`, '://')) IN (:includeTypes) AND (" +
|
||||
"(:manuallySorted AND pinned > 1) OR " +
|
||||
"(:automaticallySorted AND pinned = 1) OR" +
|
||||
"(:frequentlyUsed AND pinned = 0 AND launchCount > 0)" +
|
||||
") ORDER BY pinned DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||
)
|
||||
fun getFavoritesWithTypes(
|
||||
includeTypes: List<String>,
|
||||
manuallySorted: Boolean = false,
|
||||
@@ -46,12 +49,14 @@ interface SearchDao {
|
||||
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)" +
|
||||
") ORDER BY pinned DESC, launchCount DESC LIMIT :limit")
|
||||
@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)" +
|
||||
") ORDER BY pinned DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||
)
|
||||
fun getFavoritesWithoutTypes(
|
||||
excludeTypes: List<String>,
|
||||
manuallySorted: Boolean = false,
|
||||
@@ -114,8 +119,10 @@ interface SearchDao {
|
||||
fun incrementExistingLaunchCount(key: String)
|
||||
|
||||
@Transaction
|
||||
fun incrementLaunchCount(item: SavedSearchableEntity) {
|
||||
fun incrementLaunchCount(item: SavedSearchableEntity, alpha: Double) {
|
||||
incrementExistingLaunchCount(item.key)
|
||||
increaseWeightWhere(item.key, alpha)
|
||||
reduceWeightExcept(item.key, alpha)
|
||||
insertSkipExisting(item)
|
||||
}
|
||||
|
||||
@@ -140,9 +147,18 @@ interface SearchDao {
|
||||
@Query("UPDATE Searchable SET `pinned` = 0")
|
||||
fun unpinAll()
|
||||
|
||||
@Query("UPDATE Searchable Set `pinned` = 0, `launchCount` = 0 WHERE `key` = :key")
|
||||
@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)
|
||||
}
|
||||
|
||||
@@ -11,5 +11,6 @@ data class SavedSearchableEntity(
|
||||
@ColumnInfo(name = "searchable") val serializedSearchable: String,
|
||||
var launchCount: Int,
|
||||
@ColumnInfo(name = "pinned") var pinPosition: Int,
|
||||
var hidden: Boolean
|
||||
var hidden: Boolean,
|
||||
@ColumnInfo(defaultValue = "0.0") var weight: Double
|
||||
)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package de.mm20.launcher2.database.migrations
|
||||
|
||||
import android.util.Log
|
||||
import androidx.core.database.getIntOrNull
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration_21_22: Migration(21, 22) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("""
|
||||
ALTER TABLE `Searchable`
|
||||
ADD `weight` DOUBLE NOT NULL DEFAULT 0.0
|
||||
""")
|
||||
|
||||
database.query("""
|
||||
SELECT MAX(`launchCount`)
|
||||
FROM `Searchable`
|
||||
""")
|
||||
.runCatching {
|
||||
|
||||
if (!this.moveToFirst()) {
|
||||
return
|
||||
}
|
||||
|
||||
this.getIntOrNull(0)
|
||||
?.run {
|
||||
database.execSQL("""
|
||||
UPDATE `Searchable`
|
||||
SET `weight` = `launchCount` / $this
|
||||
""")
|
||||
}
|
||||
|
||||
}.onFailure {
|
||||
Log.e("Migration_21_22", "Setting default values for weight failed", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user