Refactor widgets

This commit is contained in:
MM20
2023-04-10 01:15:27 +02:00
parent 2054386179
commit 3cda75bc77
37 changed files with 1153 additions and 347 deletions
@@ -0,0 +1,493 @@
{
"formatVersion": 1,
"database": {
"version": 23,
"identityHash": "cb1e4fa1db90a1afddc085714570430d",
"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, `pinned` INTEGER NOT NULL, `hidden` INTEGER NOT NULL, `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
},
{
"fieldPath": "pinPosition",
"columnName": "pinned",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "hidden",
"columnName": "hidden",
"affinity": "INTEGER",
"notNull": true
},
{
"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, 'cb1e4fa1db90a1afddc085714570430d')"
]
}
}
@@ -21,10 +21,13 @@ 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_22_23
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
import de.mm20.launcher2.database.migrations.Migration_9_10
import de.mm20.launcher2.ktx.toBytes
import java.util.UUID
@Database(
entities = [
@@ -36,7 +39,7 @@ import de.mm20.launcher2.database.migrations.Migration_9_10
WidgetEntity::class,
CustomAttributeEntity::class,
SearchActionEntity::class
], version = 22, exportSchema = true
], version = 23, exportSchema = true
)
@TypeConverters(ComponentNameConverter::class, StringListConverter::class)
abstract class AppDatabase : RoomDatabase() {
@@ -81,10 +84,15 @@ abstract class AppDatabase : RoomDatabase() {
)
db.execSQL(
"INSERT INTO Widget (type, data, height, position, label) VALUES " +
"('internal', 'weather', -1, 0, '${context.getString(R.string.widget_name_weather)}')," +
"('internal', 'music', -1, 1, '${context.getString(R.string.widget_name_music)}')," +
"('internal', 'calendar', -1, 2, '${context.getString(R.string.widget_name_calendar)}');"
"INSERT INTO Widget (`type`, `position`, `id`) VALUES " +
"('weather', 0, ?)," +
"('music', 1, ?)," +
"('calendar', 2, ?);",
arrayOf(
UUID.randomUUID().toBytes(),
UUID.randomUUID().toBytes(),
UUID.randomUUID().toBytes()
)
)
}
})
@@ -104,7 +112,8 @@ abstract class AppDatabase : RoomDatabase() {
Migration_18_19(),
Migration_19_20(),
Migration_20_21(),
Migration_21_22()
Migration_21_22(),
Migration_22_23(),
).build()
if (_instance == null) _instance = instance
return instance
@@ -1,42 +1,54 @@
package de.mm20.launcher2.database
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import de.mm20.launcher2.database.entities.WidgetEntity
import de.mm20.launcher2.database.entities.PartialWidgetEntity
import kotlinx.coroutines.flow.Flow
import java.util.UUID
@Dao
interface WidgetDao {
@Query("SELECT * FROM Widget ORDER BY position ASC")
fun getWidgets(): Flow<List<WidgetEntity>>
@Query("SELECT * FROM Widget WHERE parentId IS NULL ORDER BY position ASC LIMIT :limit OFFSET :offset")
fun queryRoot(limit: Int, offset: Int): Flow<List<WidgetEntity>>
@Transaction
fun updateWidgets(widgets: List<WidgetEntity>) {
deleteAll()
insertAll(widgets)
}
@Query("SELECT * FROM Widget WHERE parentId = :parentId ORDER BY position ASC LIMIT :limit OFFSET :offset")
fun queryByParent(parentId: UUID,limit: Int, offset: Int): Flow<List<WidgetEntity>>
@Insert
fun insertAll(widgets: List<WidgetEntity>)
suspend fun insert(widget: WidgetEntity)
@Insert
fun insert(widget: WidgetEntity)
suspend fun insert(widgets: List<WidgetEntity>)
@Query("DELETE FROM Widget")
fun deleteAll()
@Update(entity = WidgetEntity::class)
suspend fun patch(widget: PartialWidgetEntity)
@Update(entity = WidgetEntity::class)
suspend fun patch(widgets: List<PartialWidgetEntity>)
@Query("DELETE FROM Widget WHERE data = :data AND type = :type")
fun deleteWidget(type: String, data: String)
@Update
suspend fun update(widget: WidgetEntity)
@Query("UPDATE Widget SET height = :newHeight WHERE data = :data AND type = :type")
fun updateHeight(type: String, data: String, newHeight: Int)
@Update
suspend fun update(widgets: List<WidgetEntity>)
@Query("SELECT EXISTS(SELECT 1 FROM Widget WHERE type = :type AND data = :data)")
fun exists(type: String, data: String) : Flow<Boolean>
@Query("DELETE FROM Widget WHERE id = :id")
suspend fun delete(id: UUID)
@Query("DELETE FROM WIDGET WHERE id IN (:ids)")
suspend fun delete(ids: List<UUID>)
@Query("DELETE FROM Widget WHERE parentId = :parentId")
suspend fun deleteByParent(parentId: UUID)
@Query("DELETE FROM Widget WHERE parentId IS NULL")
suspend fun deleteRoot()
@Query("SELECT EXISTS(SELECT 1 FROM Widget WHERE type = :type)")
fun exists(type: String): Flow<Boolean>
@Query("SELECT * FROM Widget ORDER BY position ASC LIMIT 1")
fun getFirst() : Flow<WidgetEntity?>
}
@@ -2,14 +2,23 @@ package de.mm20.launcher2.database.entities
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.util.UUID
@Entity(tableName = "Widget")
data class WidgetEntity(
val type: String,
var data: String,
var height: Int,
var config: String?,
var position: Int,
val label: String = "",
@PrimaryKey(autoGenerate = true) val id: Int? = null
@PrimaryKey val id: UUID,
val parentId: UUID? = null,
)
/**
* Partial entity for updating and deleting
*/
data class PartialWidgetEntity(
val type: String,
var config: String?,
@PrimaryKey val id: UUID,
)
@@ -0,0 +1,44 @@
package de.mm20.launcher2.database.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import de.mm20.launcher2.ktx.toBytes
import org.koin.core.component.KoinComponent
import java.util.UUID
class Migration_22_23 : Migration(22, 23), KoinComponent {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Widget RENAME TO Widget_old")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Widget` (
`type` TEXT NOT NULL,
`config` TEXT,
`position` INTEGER NOT NULL,
`id` BLOB NOT NULL,
`parentId` BLOB,
PRIMARY KEY(`id`)
)
"""
)
val oldWidgets =
database.query("SELECT `type`, `data`, `height`, `position` FROM `Widget_old`")
while (oldWidgets.moveToNext()) {
val oldType = oldWidgets.getString(0)
val data = oldWidgets.getString(1)
val newType = if (oldType == "3rdparty") "app" else data
val height = oldWidgets.getInt(2)
val position = oldWidgets.getInt(3)
val id = UUID.randomUUID()
val config = if (oldType == "3rdparty") {
"{\"widgetId\": $data, \"height\": $height}"
} else null
database.execSQL(
"INSERT INTO `Widget` (`type`, `config`, `position`, `id`) VALUES (?, ?, ?, ?)",
arrayOf(newType, config, position, id.toBytes())
)
}
oldWidgets.close()
database.execSQL("DROP TABLE Widget_old")
}
}
@@ -0,0 +1,12 @@
package de.mm20.launcher2.ktx
import java.nio.ByteBuffer
import java.util.UUID
fun UUID.toBytes(): ByteArray {
val bytes = ByteArray(16)
val buffer = ByteBuffer.wrap(bytes)
buffer.putLong(mostSignificantBits)
buffer.putLong(leastSignificantBits)
return buffer.array()
}