Restructure / cleanup / improve icon pack handling
This commit is contained in:
parent
96f69356c4
commit
7168169e35
@ -237,13 +237,23 @@ private fun IconLayer(
|
||||
) {
|
||||
when (layer) {
|
||||
is ClockLayer -> {
|
||||
ClockLayer(layer.sublayers, scale = layer.scale, tintColor = null)
|
||||
ClockLayer(
|
||||
layer.sublayers,
|
||||
scale = layer.scale,
|
||||
defaultSecond = layer.defaultSecond,
|
||||
defaultMinute = layer.defaultMinute,
|
||||
defaultHour = layer.defaultHour,
|
||||
tintColor = null
|
||||
)
|
||||
}
|
||||
|
||||
is TintedClockLayer -> {
|
||||
ClockLayer(
|
||||
layer.sublayers,
|
||||
scale = layer.scale,
|
||||
defaultSecond = layer.defaultSecond,
|
||||
defaultMinute = layer.defaultMinute,
|
||||
defaultHour = layer.defaultHour,
|
||||
tintColor = if (layer.color == 0) defaultTintColor
|
||||
else Color(getTone(layer.color, colorTone))
|
||||
)
|
||||
@ -323,6 +333,9 @@ private fun getTone(argb: Int, tone: Int): Int {
|
||||
@Composable
|
||||
private fun ClockLayer(
|
||||
sublayers: List<ClockSublayer>,
|
||||
defaultMinute: Int,
|
||||
defaultHour: Int,
|
||||
defaultSecond: Int,
|
||||
scale: Float,
|
||||
tintColor: Color?,
|
||||
) {
|
||||
@ -331,21 +344,22 @@ private fun ClockLayer(
|
||||
}
|
||||
|
||||
val second = remember {
|
||||
Animatable(time.second.toFloat())
|
||||
Animatable((time.second - defaultSecond).toFloat())
|
||||
}
|
||||
|
||||
val minute = remember {
|
||||
Animatable(time.minute.toFloat() + time.second.toFloat() / 60f)
|
||||
Animatable((time.minute - defaultMinute).toFloat() + (time.second - defaultSecond).toFloat() / 60f)
|
||||
}
|
||||
|
||||
val hour = remember {
|
||||
Animatable(time.hour.toFloat() + time.minute.toFloat() / 60f)
|
||||
Animatable((time.hour - defaultHour).toFloat() + (time.minute + defaultMinute).toFloat() / 60f)
|
||||
}
|
||||
|
||||
LaunchedEffect(time) {
|
||||
val h = time.hour.toFloat() + time.minute.toFloat() / 60f
|
||||
val m = time.minute.toFloat() + time.second.toFloat() / 60f
|
||||
val s = time.second.toFloat() + (time.nano / 1000000f) / 1000f
|
||||
val h = (time.hour - defaultHour).toFloat() + (time.minute - defaultSecond).toFloat() / 60f
|
||||
val m =
|
||||
(time.minute - defaultMinute).toFloat() + (time.second - defaultSecond).toFloat() / 60f
|
||||
val s = (time.second - defaultSecond).toFloat() + (time.nano / 1000000f) / 1000f
|
||||
second.snapTo(s)
|
||||
hour.snapTo(h)
|
||||
minute.snapTo(m)
|
||||
|
||||
@ -15,6 +15,9 @@ data class ColorLayer(
|
||||
|
||||
data class ClockLayer(
|
||||
val sublayers: List<ClockSublayer>,
|
||||
val defaultHour: Int = 0,
|
||||
val defaultMinute: Int = 0,
|
||||
val defaultSecond: Int = 0,
|
||||
val scale: Float,
|
||||
) : LauncherIconLayer
|
||||
|
||||
@ -38,6 +41,9 @@ data class TintedIconLayer(
|
||||
|
||||
data class TintedClockLayer(
|
||||
val sublayers: List<ClockSublayer>,
|
||||
val defaultHour: Int = 0,
|
||||
val defaultMinute: Int = 0,
|
||||
val defaultSecond: Int = 0,
|
||||
val scale: Float,
|
||||
val color: Int = 0,
|
||||
) : LauncherIconLayer
|
||||
|
||||
@ -48,7 +48,7 @@ dependencies {
|
||||
implementation(libs.androidx.appcompat)
|
||||
api(libs.androidx.roomruntime)
|
||||
kapt(libs.androidx.roomcompiler)
|
||||
implementation(libs.androidx.room)
|
||||
api(libs.androidx.room)
|
||||
implementation(libs.koin.android)
|
||||
|
||||
implementation(project(":core:i18n"))
|
||||
|
||||
@ -0,0 +1,492 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 21,
|
||||
"identityHash": "5fa8da4798ebb9b966ac8d260c5bbee3",
|
||||
"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, 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
|
||||
}
|
||||
],
|
||||
"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, `data` TEXT NOT NULL, `height` INTEGER NOT NULL, `position` INTEGER NOT NULL, `label` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "data",
|
||||
"columnName": "data",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "height",
|
||||
"columnName": "height",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "position",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "label",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"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, '5fa8da4798ebb9b966ac8d260c5bbee3')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,7 @@ 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
|
||||
@ -19,6 +20,7 @@ import de.mm20.launcher2.database.migrations.Migration_16_17
|
||||
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_6_7
|
||||
import de.mm20.launcher2.database.migrations.Migration_7_8
|
||||
import de.mm20.launcher2.database.migrations.Migration_8_9
|
||||
@ -34,7 +36,7 @@ import de.mm20.launcher2.database.migrations.Migration_9_10
|
||||
WidgetEntity::class,
|
||||
CustomAttributeEntity::class,
|
||||
SearchActionEntity::class,
|
||||
], version = 20, exportSchema = true
|
||||
], version = 21, exportSchema = true
|
||||
)
|
||||
@TypeConverters(ComponentNameConverter::class, StringListConverter::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
@ -101,6 +103,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration_17_18(),
|
||||
Migration_18_19(),
|
||||
Migration_19_20(),
|
||||
Migration_20_21(),
|
||||
).build()
|
||||
if (_instance == null) _instance = instance
|
||||
return instance
|
||||
|
||||
@ -5,21 +5,20 @@ import androidx.room.*
|
||||
import de.mm20.launcher2.database.entities.IconEntity
|
||||
import de.mm20.launcher2.database.entities.IconPackEntity
|
||||
|
||||
internal val AppTypes = listOf("app", "calendar", "clock")
|
||||
|
||||
@Dao
|
||||
interface IconDao {
|
||||
@Insert
|
||||
fun insertAll(icons: List<IconEntity>)
|
||||
suspend fun insertAll(icons: List<IconEntity>)
|
||||
|
||||
@Query("SELECT drawable FROM Icons WHERE componentName = :componentName AND iconPack = :iconPack")
|
||||
suspend fun getIconName(componentName: String, iconPack: String): String?
|
||||
@Query("SELECT * FROM Icons WHERE packageName = :packageName AND (activityName = :activityName OR activityName IS NULL) AND iconPack = :iconPack AND type IN ('app', 'calendar', 'clock') LIMIT 1")
|
||||
suspend fun getIcon(packageName: String, activityName: String?, iconPack: String): IconEntity?
|
||||
|
||||
@Query("SELECT * FROM Icons WHERE componentName = :componentName AND iconPack = :iconPack AND (type = 'app' OR type = 'calendar') LIMIT 1")
|
||||
suspend fun getIcon(componentName: String, iconPack: String): IconEntity?
|
||||
@Query("SELECT * FROM Icons WHERE packageName = :packageName AND (activityName = :activityName OR activityName IS NULL) AND type IN ('app', 'calendar', 'clock')")
|
||||
suspend fun getIconsFromAllPacks(packageName: String, activityName: String): List<IconEntity>
|
||||
|
||||
@Query("SELECT * FROM Icons WHERE componentName = :componentName AND (type = 'app' OR type = 'calendar')")
|
||||
suspend fun getIconsFromAllPacks(componentName: String): List<IconEntity>
|
||||
|
||||
@Query("SELECT * FROM Icons WHERE (type = 'app' OR type = 'calendar') AND (drawable LIKE :drawableQuery OR componentName LIKE :componentQuery OR name LIKE :nameQuery) AND (:iconPack IS NULL OR iconPack = :iconPack) ORDER BY iconPack, drawable LIMIT :limit")
|
||||
@Query("SELECT * FROM Icons WHERE type IN ('app', 'calendar', 'clock') AND (drawable LIKE :drawableQuery OR packageName LIKE :componentQuery OR activityName LIKE :componentQuery OR name LIKE :nameQuery) AND (:iconPack IS NULL OR iconPack = :iconPack) ORDER BY iconPack, drawable LIMIT :limit")
|
||||
suspend fun searchIconPackIcons(
|
||||
componentQuery: String,
|
||||
nameQuery: String,
|
||||
@ -28,30 +27,10 @@ interface IconDao {
|
||||
limit: Int = 100
|
||||
): List<IconEntity>
|
||||
|
||||
@Query("SELECT * FROM Icons WHERE (type = 'greyscale_icon') AND componentName LIKE :query GROUP BY componentName ORDER BY drawable LIMIT :limit")
|
||||
suspend fun searchGreyscaleIcons(query: String, limit: Int = 100): List<IconEntity>
|
||||
|
||||
@Query("DELETE FROM Icons WHERE iconPack = :iconPack AND type != 'greyscale_icon'")
|
||||
@Query("DELETE FROM Icons WHERE iconPack = :iconPack")
|
||||
fun deleteIcons(iconPack: String)
|
||||
|
||||
@Query("DELETE FROM Icons WHERE iconPack = :iconPack AND type = 'greyscale_icon'")
|
||||
fun deleteGrayscaleIcons(iconPack: String)
|
||||
|
||||
@Transaction
|
||||
suspend fun installIconPack(iconPack: IconPackEntity, icons: List<IconEntity>) {
|
||||
deleteIconPack(iconPack)
|
||||
deleteIcons(iconPack.packageName)
|
||||
insertAll(icons)
|
||||
installIconPack(iconPack)
|
||||
}
|
||||
|
||||
@Transaction
|
||||
suspend fun installGrayscaleIconMap(packageName: String, icons: List<IconEntity>) {
|
||||
deleteGrayscaleIcons(packageName)
|
||||
insertAll(icons)
|
||||
}
|
||||
|
||||
@Insert
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun installIconPack(iconPack: IconPackEntity)
|
||||
|
||||
@Query("SELECT * FROM IconPack")
|
||||
@ -60,35 +39,9 @@ interface IconDao {
|
||||
@Query("SELECT * FROM IconPack WHERE packageName = :packageName LIMIT 1")
|
||||
suspend fun getIconPack(packageName: String): IconPackEntity?
|
||||
|
||||
@Query("SELECT * FROM IconPack")
|
||||
fun getInstalledIconPacksLiveData(): LiveData<List<IconPackEntity>>
|
||||
|
||||
@Delete
|
||||
fun deleteIconPack(iconPack: IconPackEntity)
|
||||
|
||||
@Query("SELECT * FROM IconPack WHERE packageName = :packageName AND version = :version")
|
||||
suspend fun getPacks(packageName: String, version: String): List<IconPackEntity>
|
||||
|
||||
@Transaction
|
||||
suspend fun isInstalled(iconPack: IconPackEntity): Boolean {
|
||||
return getPacks(iconPack.packageName, iconPack.version).isNotEmpty()
|
||||
}
|
||||
|
||||
@Query("DELETE FROM Icons WHERE iconPack NOT IN (:packs) AND (type = 'calendar' OR type = 'app')")
|
||||
fun deleteAllIconsPackIconsExcept(packs: List<String>)
|
||||
|
||||
@Query("DELETE FROM Icons WHERE iconPack NOT IN (:packs) AND type = 'greyscale_icon'")
|
||||
fun deleteAllGrayscaleIconsExcept(packs: List<String>)
|
||||
|
||||
@Query("DELETE FROM IconPack WHERE packageName NOT IN (:packs)")
|
||||
fun deleteAllPacksExcept(packs: List<String>)
|
||||
|
||||
@Transaction
|
||||
fun uninstallIconPacksExcept(packs: List<String>) {
|
||||
deleteAllIconsPackIconsExcept(packs)
|
||||
deleteAllPacksExcept(packs)
|
||||
}
|
||||
|
||||
@Query("SELECT drawable FROM Icons WHERE iconPack = :pack AND type = 'iconback'")
|
||||
suspend fun getIconBacks(pack: String): List<String>
|
||||
|
||||
@ -100,7 +53,4 @@ interface IconDao {
|
||||
|
||||
@Query("SELECT scale FROM IconPack WHERE packageName = :pack")
|
||||
suspend fun getScale(pack: String): Float?
|
||||
|
||||
@Query("SELECT * FROM Icons WHERE type = 'greyscale_icon' AND componentName = :componentName")
|
||||
suspend fun getGreyscaleIcon(componentName: String): IconEntity?
|
||||
}
|
||||
@ -1,16 +1,17 @@
|
||||
package de.mm20.launcher2.database.entities
|
||||
|
||||
import android.content.ComponentName
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "Icons")
|
||||
data class IconEntity(
|
||||
val type: String,
|
||||
val componentName: ComponentName?,
|
||||
val packageName: String? = null,
|
||||
val activityName: String? = null,
|
||||
val drawable: String?,
|
||||
val extras: String? = null,
|
||||
val iconPack: String,
|
||||
val name: String?,
|
||||
val name: String? = null,
|
||||
val themed: Boolean = false,
|
||||
@PrimaryKey(autoGenerate = true) val id : Long? = null
|
||||
)
|
||||
@ -0,0 +1,23 @@
|
||||
package de.mm20.launcher2.database.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration_20_21: Migration(20, 21) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("DROP TABLE `Icons`")
|
||||
database.execSQL("DELETE FROM `IconPack`")
|
||||
database.execSQL("""
|
||||
CREATE TABLE IF NOT EXISTS `Icons` (
|
||||
`type` TEXT NOT NULL,
|
||||
`packageName` TEXT,
|
||||
`activityName` TEXT,
|
||||
`drawable` TEXT,
|
||||
`extras` TEXT,
|
||||
`iconPack` TEXT NOT NULL,
|
||||
`name` TEXT,
|
||||
`themed` INTEGER NOT NULL DEFAULT 0,
|
||||
`id` INTEGER PRIMARY KEY AUTOINCREMENT)
|
||||
""")
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package de.mm20.launcher2.data.customattrs
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.util.Log
|
||||
import de.mm20.launcher2.database.entities.CustomAttributeEntity
|
||||
import de.mm20.launcher2.ktx.jsonObjectOf
|
||||
@ -76,10 +77,20 @@ sealed class CustomIcon : CustomAttribute {
|
||||
val type = payload.getString("type")
|
||||
return when (type) {
|
||||
"custom_icon_pack_icon" -> {
|
||||
CustomIconPackIcon(
|
||||
iconComponentName = payload.getString("icon"),
|
||||
iconPackPackage = payload.getString("icon_pack")
|
||||
)
|
||||
val legacyComponentName = payload.optString("icon").let { ComponentName.unflattenFromString(it) }
|
||||
if (legacyComponentName != null) {
|
||||
CustomIconPackIcon(
|
||||
iconPackageName = legacyComponentName.packageName,
|
||||
iconActivityName = legacyComponentName.className,
|
||||
iconPackPackage = payload.getString("icon_pack")
|
||||
)
|
||||
} else {
|
||||
CustomIconPackIcon(
|
||||
iconPackageName = payload.optString("package").takeIf { it.isNotEmpty() } ?: return null,
|
||||
iconActivityName = payload.optString("activity").takeIf { it.isNotEmpty() },
|
||||
iconPackPackage = payload.getString("icon_pack")
|
||||
)
|
||||
}
|
||||
}
|
||||
"custom_themed_icon" -> {
|
||||
CustomThemedIcon(
|
||||
@ -105,12 +116,14 @@ sealed class CustomIcon : CustomAttribute {
|
||||
|
||||
data class CustomIconPackIcon(
|
||||
val iconPackPackage: String,
|
||||
val iconComponentName: String,
|
||||
val iconPackageName: String,
|
||||
val iconActivityName: String?,
|
||||
) : CustomIcon() {
|
||||
override fun toDatabaseValue(): String {
|
||||
return jsonObjectOf(
|
||||
"type" to "custom_icon_pack_icon",
|
||||
"icon" to iconComponentName,
|
||||
"package" to iconPackageName,
|
||||
"activity" to iconActivityName,
|
||||
"icon_pack" to iconPackPackage,
|
||||
).toString()
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package de.mm20.launcher2.icons
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.ResolveInfo
|
||||
import de.mm20.launcher2.database.entities.IconPackEntity
|
||||
|
||||
@ -8,13 +9,13 @@ data class IconPack(
|
||||
val name: String,
|
||||
val packageName: String,
|
||||
val version: String,
|
||||
var scale: Float = 1f,
|
||||
val scale: Float = 1f,
|
||||
val themed: Boolean = false,
|
||||
) {
|
||||
constructor(entity: IconPackEntity) : this(
|
||||
name = entity.name,
|
||||
packageName = entity.packageName,
|
||||
version = entity.packageName,
|
||||
version = entity.version,
|
||||
scale = entity.scale,
|
||||
themed = entity.themed,
|
||||
)
|
||||
@ -30,6 +31,17 @@ data class IconPack(
|
||||
themed = themed,
|
||||
)
|
||||
|
||||
internal constructor(
|
||||
context: Context,
|
||||
packageInfo: PackageInfo,
|
||||
themed: Boolean = false
|
||||
): this(
|
||||
name = packageInfo.applicationInfo.loadLabel(context.packageManager).toString(),
|
||||
packageName = packageInfo.packageName,
|
||||
version = context.packageManager.getPackageInfo(packageInfo.packageName, 0).versionName,
|
||||
themed = themed,
|
||||
)
|
||||
|
||||
fun toDatabaseEntity(): IconPackEntity {
|
||||
return IconPackEntity(
|
||||
name = name,
|
||||
|
||||
@ -1,33 +1,188 @@
|
||||
package de.mm20.launcher2.icons
|
||||
|
||||
import android.content.ComponentName
|
||||
import de.mm20.launcher2.database.entities.IconEntity
|
||||
import de.mm20.launcher2.icons.compat.ClockIconConfig
|
||||
import de.mm20.launcher2.ktx.jsonObjectOf
|
||||
import org.json.JSONObject
|
||||
|
||||
data class IconPackIcon(
|
||||
val type: String,
|
||||
val componentName: ComponentName?,
|
||||
val drawable: String?,
|
||||
val iconPack: String,
|
||||
val themed: Boolean = false,
|
||||
val name: String? = null,
|
||||
) {
|
||||
constructor(entity: IconEntity) : this(
|
||||
type = entity.type,
|
||||
componentName = entity.componentName,
|
||||
drawable = entity.drawable,
|
||||
iconPack = entity.iconPack,
|
||||
name = entity.name,
|
||||
themed = entity.themed,
|
||||
)
|
||||
sealed interface IconPackComponent {
|
||||
val iconPack: String
|
||||
fun toDatabaseEntity(): IconEntity
|
||||
}
|
||||
|
||||
fun toDatabaseEntity(): IconEntity {
|
||||
sealed interface IconPackAppIcon: IconPackComponent {
|
||||
val packageName: String
|
||||
val activityName: String?
|
||||
val name: String?
|
||||
val themed: Boolean
|
||||
}
|
||||
|
||||
data class IconBack(
|
||||
val drawable: String,
|
||||
override val iconPack: String,
|
||||
): IconPackComponent {
|
||||
override fun toDatabaseEntity(): IconEntity {
|
||||
return IconEntity(
|
||||
type = type,
|
||||
componentName = componentName,
|
||||
type = "iconback",
|
||||
drawable = drawable,
|
||||
iconPack = iconPack,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class IconUpon(
|
||||
val drawable: String,
|
||||
override val iconPack: String,
|
||||
): IconPackComponent {
|
||||
override fun toDatabaseEntity(): IconEntity {
|
||||
return IconEntity(
|
||||
type = "iconupon",
|
||||
drawable = drawable,
|
||||
iconPack = iconPack,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class IconMask(
|
||||
val drawable: String,
|
||||
override val iconPack: String,
|
||||
): IconPackComponent {
|
||||
override fun toDatabaseEntity(): IconEntity {
|
||||
return IconEntity(
|
||||
type = "iconmask",
|
||||
drawable = drawable,
|
||||
iconPack = iconPack,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class AppIcon(
|
||||
val drawable: String,
|
||||
override val iconPack: String,
|
||||
override val packageName: String,
|
||||
override val activityName: String?,
|
||||
override val name: String?,
|
||||
override val themed: Boolean = false,
|
||||
): IconPackComponent, IconPackAppIcon {
|
||||
override fun toDatabaseEntity(): IconEntity {
|
||||
return IconEntity(
|
||||
type = "app",
|
||||
packageName = packageName,
|
||||
activityName = activityName,
|
||||
drawable = drawable,
|
||||
name = name,
|
||||
iconPack = iconPack,
|
||||
themed = themed,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class CalendarIcon(
|
||||
val drawables: List<String>,
|
||||
override val iconPack: String,
|
||||
override val packageName: String,
|
||||
override val activityName: String? = null,
|
||||
override val name: String? = null,
|
||||
override val themed: Boolean = false,
|
||||
): IconPackComponent, IconPackAppIcon {
|
||||
override fun toDatabaseEntity(): IconEntity {
|
||||
return IconEntity(
|
||||
type = "calendar",
|
||||
drawable = drawables.joinToString(","),
|
||||
iconPack = iconPack,
|
||||
packageName = packageName,
|
||||
activityName = activityName,
|
||||
name = name,
|
||||
themed = themed,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class ClockIcon(
|
||||
val drawable: String,
|
||||
override val iconPack: String,
|
||||
override val packageName: String,
|
||||
override val activityName: String? = null,
|
||||
override val name: String? = null,
|
||||
override val themed: Boolean,
|
||||
val config: ClockIconConfig,
|
||||
): IconPackComponent, IconPackAppIcon {
|
||||
override fun toDatabaseEntity(): IconEntity {
|
||||
return IconEntity(
|
||||
type = "clock",
|
||||
packageName = packageName,
|
||||
activityName = activityName,
|
||||
drawable = drawable,
|
||||
name = name,
|
||||
iconPack = iconPack,
|
||||
themed = themed,
|
||||
extras = jsonObjectOf(
|
||||
"defaultSecond" to config.defaultSecond,
|
||||
"defaultMinute" to config.defaultMinute,
|
||||
"defaultHour" to config.defaultHour,
|
||||
"hourLayer" to config.hourLayer,
|
||||
"minuteLayer" to config.minuteLayer,
|
||||
"secondLayer" to config.secondLayer,
|
||||
).toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun Icon(entity: IconEntity): IconPackComponent? {
|
||||
return when(entity.type) {
|
||||
"iconback" -> IconBack(
|
||||
drawable = entity.drawable ?: return null,
|
||||
iconPack = entity.iconPack,
|
||||
)
|
||||
"iconupon" -> IconUpon(
|
||||
drawable = entity.drawable ?: return null,
|
||||
iconPack = entity.iconPack,
|
||||
)
|
||||
"iconmask" -> IconMask(
|
||||
drawable = entity.drawable ?: return null,
|
||||
iconPack = entity.iconPack,
|
||||
)
|
||||
"app" -> AppIcon(
|
||||
drawable = entity.drawable ?: return null,
|
||||
iconPack = entity.iconPack,
|
||||
packageName = entity.packageName ?: return null,
|
||||
activityName = entity.activityName,
|
||||
themed = entity.themed,
|
||||
name = entity.name,
|
||||
)
|
||||
"calendar" -> CalendarIcon(
|
||||
drawables = entity.drawable?.split(",") ?: return null,
|
||||
iconPack = entity.iconPack,
|
||||
themed = entity.themed,
|
||||
packageName = entity.packageName ?: return null,
|
||||
activityName = entity.activityName,
|
||||
name = entity.name,
|
||||
)
|
||||
"clock" -> {
|
||||
val config = JSONObject(entity.extras ?: return null)
|
||||
ClockIcon(
|
||||
drawable = entity.drawable!!,
|
||||
iconPack = entity.iconPack,
|
||||
packageName = entity.packageName ?: return null,
|
||||
name = entity.name,
|
||||
activityName = entity.activityName,
|
||||
themed = entity.themed,
|
||||
config = ClockIconConfig(
|
||||
defaultSecond = config.optInt("defaultSecond", 0),
|
||||
defaultMinute = config.optInt("defaultMinute", 0),
|
||||
defaultHour = config.optInt("defaultHour", 0),
|
||||
hourLayer = config.optInt("hourLayer", 0),
|
||||
minuteLayer = config.optInt("minuteLayer", 0),
|
||||
secondLayer = config.optInt("secondLayer", 0),
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun IconPackAppIcon(entity: IconEntity): IconPackAppIcon? {
|
||||
if (entity.type != "app" && entity.type != "calendar" && entity.type != "clock") return null
|
||||
return Icon(entity) as? IconPackAppIcon
|
||||
}
|
||||
|
||||
@ -18,14 +18,14 @@ import android.graphics.drawable.RotateDrawable
|
||||
import android.util.Log
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.database.AppDatabase
|
||||
import de.mm20.launcher2.icons.loaders.GrayscaleMapInstaller
|
||||
import de.mm20.launcher2.icons.loaders.IconPackInstaller
|
||||
import de.mm20.launcher2.icons.loaders.AppFilterIconPackInstaller
|
||||
import de.mm20.launcher2.icons.loaders.GrayscaleMapIconPackInstaller
|
||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||
import de.mm20.launcher2.ktx.obtainTypedArrayOrNull
|
||||
import de.mm20.launcher2.ktx.randomElementOrNull
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@ -50,16 +50,33 @@ class IconPackManager(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateIconPacks() {
|
||||
withContext(Dispatchers.IO) {
|
||||
IconPackInstaller(context, appDatabase).installIcons()
|
||||
GrayscaleMapInstaller(context, appDatabase).installIcons()
|
||||
private var updateIconPacksMutex = Mutex()
|
||||
suspend fun updateIconPacks(): Boolean {
|
||||
var iconsHaveBeenUpdated = false
|
||||
updateIconPacksMutex.lock()
|
||||
val installers = listOf(
|
||||
AppFilterIconPackInstaller(context, appDatabase),
|
||||
GrayscaleMapIconPackInstaller(context, appDatabase),
|
||||
)
|
||||
for (installer in installers) {
|
||||
val iconPacks = installer.getInstalledIconPacks()
|
||||
for (pack in iconPacks) {
|
||||
if (!installer.isInstalledAndUpToDate(pack)) {
|
||||
installer.install(pack)
|
||||
iconsHaveBeenUpdated = true
|
||||
} else {
|
||||
Log.d("MM20", "Icon pack ${pack.packageName} is up to date")
|
||||
}
|
||||
}
|
||||
}
|
||||
updateIconPacksMutex.unlock()
|
||||
return iconsHaveBeenUpdated
|
||||
}
|
||||
|
||||
suspend fun getIcon(
|
||||
iconPack: String,
|
||||
componentName: ComponentName,
|
||||
packageName: String,
|
||||
activityName: String?,
|
||||
): LauncherIcon? {
|
||||
val res = try {
|
||||
context.packageManager.getResourcesForApplication(iconPack)
|
||||
@ -68,69 +85,17 @@ class IconPackManager(
|
||||
return null
|
||||
}
|
||||
val iconDao = appDatabase.iconDao()
|
||||
val icon = iconDao.getIcon(componentName.flattenToString(), iconPack)
|
||||
val icon = iconDao.getIcon(packageName, activityName, iconPack)?.let { IconPackAppIcon(it) }
|
||||
?: return null
|
||||
|
||||
val drawableName = icon.drawable ?: return null
|
||||
|
||||
if (icon.type == "calendar") {
|
||||
return getIconPackCalendarIcon(context, iconPack, drawableName, icon.themed)
|
||||
}
|
||||
val resId = res.getIdentifier(drawableName, "drawable", iconPack).takeIf { it != 0 }
|
||||
?: return null
|
||||
val drawable = try {
|
||||
ResourcesCompat.getDrawable(res, resId, context.theme) ?: return null
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
return null
|
||||
}
|
||||
return when {
|
||||
icon.themed && drawable is AdaptiveIconDrawable -> {
|
||||
if (isAtLeastApiLevel(33) && drawable.monochrome != null) {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = StaticIconLayer(
|
||||
icon = drawable.monochrome!!,
|
||||
scale = 1f,
|
||||
),
|
||||
backgroundLayer = ColorLayer(),
|
||||
)
|
||||
} else {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = TintedIconLayer(
|
||||
icon = drawable.foreground,
|
||||
scale = 1.5f,
|
||||
),
|
||||
backgroundLayer = ColorLayer(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
drawable is AdaptiveIconDrawable -> {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = drawable.foreground?.let {
|
||||
StaticIconLayer(
|
||||
icon = it,
|
||||
scale = 1.5f,
|
||||
)
|
||||
} ?: TransparentLayer,
|
||||
backgroundLayer = drawable.background?.let {
|
||||
StaticIconLayer(
|
||||
icon = it,
|
||||
scale = 1.5f,
|
||||
)
|
||||
} ?: TransparentLayer,
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
StaticLauncherIcon(
|
||||
foregroundLayer = StaticIconLayer(
|
||||
icon = drawable,
|
||||
scale = 1f
|
||||
),
|
||||
backgroundLayer = TransparentLayer
|
||||
)
|
||||
}
|
||||
if (icon is CalendarIcon) {
|
||||
return getIconPackCalendarIcon(icon, res)
|
||||
} else if (icon is AppIcon) {
|
||||
return getIconPackStaticIcon(icon, res)
|
||||
} else if (icon is ClockIcon) {
|
||||
return getIconPackClockIcon(icon, res)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun generateIcon(
|
||||
@ -233,10 +198,10 @@ class IconPackManager(
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getAllIconPackIcons(componentName: ComponentName): List<IconPackIcon> {
|
||||
suspend fun getAllIconPackIcons(componentName: ComponentName): List<IconPackAppIcon> {
|
||||
val iconDao = appDatabase.iconDao()
|
||||
return iconDao.getIconsFromAllPacks(componentName.flattenToString())
|
||||
.map { IconPackIcon(it) }
|
||||
return iconDao.getIconsFromAllPacks(componentName.packageName, componentName.shortClassName)
|
||||
.mapNotNull { IconPackAppIcon(it) }
|
||||
}
|
||||
|
||||
private suspend fun getIconBack(iconPack: String): String? {
|
||||
@ -262,25 +227,90 @@ class IconPackManager(
|
||||
return iconDao.getScale(iconPack) ?: 1f
|
||||
}
|
||||
|
||||
private fun getIconPackCalendarIcon(
|
||||
context: Context,
|
||||
iconPack: String,
|
||||
baseIconName: String,
|
||||
themed: Boolean,
|
||||
private fun getIconPackStaticIcon(
|
||||
icon: AppIcon,
|
||||
resources: Resources,
|
||||
): LauncherIcon? {
|
||||
val resources = try {
|
||||
context.packageManager.getResourcesForApplication(iconPack)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
val resId =
|
||||
resources.getIdentifier(icon.drawable, "drawable", icon.iconPack).takeIf { it != 0 }
|
||||
?: return null
|
||||
val drawable = try {
|
||||
ResourcesCompat.getDrawable(resources, resId, context.theme) ?: return null
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
return null
|
||||
}
|
||||
val drawableIds = (1..31).map {
|
||||
val drawableName = baseIconName + it
|
||||
val id = resources.getIdentifier(drawableName, "drawable", iconPack)
|
||||
return when {
|
||||
icon.themed && drawable is AdaptiveIconDrawable -> {
|
||||
if (isAtLeastApiLevel(33) && drawable.monochrome != null) {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = TintedIconLayer(
|
||||
icon = drawable.monochrome!!,
|
||||
scale = 1f,
|
||||
),
|
||||
backgroundLayer = ColorLayer(),
|
||||
)
|
||||
} else {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = TintedIconLayer(
|
||||
icon = drawable.foreground,
|
||||
scale = 1.5f,
|
||||
),
|
||||
backgroundLayer = ColorLayer(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
icon.themed -> {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = TintedIconLayer(
|
||||
icon = drawable,
|
||||
scale = 0.5f,
|
||||
),
|
||||
backgroundLayer = ColorLayer(),
|
||||
)
|
||||
}
|
||||
|
||||
drawable is AdaptiveIconDrawable -> {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = drawable.foreground?.let {
|
||||
StaticIconLayer(
|
||||
icon = it,
|
||||
scale = 1.5f,
|
||||
)
|
||||
} ?: TransparentLayer,
|
||||
backgroundLayer = drawable.background?.let {
|
||||
StaticIconLayer(
|
||||
icon = it,
|
||||
scale = 1.5f,
|
||||
)
|
||||
} ?: TransparentLayer,
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
StaticLauncherIcon(
|
||||
foregroundLayer = StaticIconLayer(
|
||||
icon = drawable,
|
||||
scale = 1f
|
||||
),
|
||||
backgroundLayer = TransparentLayer
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIconPackCalendarIcon(
|
||||
icon: CalendarIcon,
|
||||
resources: Resources,
|
||||
): LauncherIcon? {
|
||||
val drawableIds = icon.drawables.map {
|
||||
val id = resources.getIdentifier(it, "drawable", icon.iconPack)
|
||||
if (id == 0) return null
|
||||
id
|
||||
}.toIntArray()
|
||||
|
||||
if (themed) {
|
||||
|
||||
if (icon.themed) {
|
||||
return ThemedDynamicCalendarIcon(
|
||||
resources = resources,
|
||||
resourceIds = drawableIds,
|
||||
@ -292,142 +322,91 @@ class IconPackManager(
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getThemedIcon(packageName: String): LauncherIcon? {
|
||||
val icon = getGreyscaleIcon(packageName) ?: return null
|
||||
val resId = icon.drawable?.toIntOrNull() ?: return null
|
||||
try {
|
||||
val resources = context.packageManager.getResourcesForApplication(icon.iconPack)
|
||||
return getThemedClockIcon(resources, resId) ?: getThemedCalendarIcon(
|
||||
resources,
|
||||
resId,
|
||||
iconProviderPackage = icon.iconPack
|
||||
) ?: getThemedStaticIcon(resources, resId)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
suspend fun getGreyscaleIcon(packageName: String): IconPackIcon? {
|
||||
val iconDao = AppDatabase.getInstance(context).iconDao()
|
||||
return iconDao.getGreyscaleIcon(ComponentName(packageName, packageName).flattenToString())
|
||||
?.let { IconPackIcon(it) }
|
||||
|
||||
}
|
||||
|
||||
private fun getThemedStaticIcon(resources: Resources, resId: Int): LauncherIcon? {
|
||||
try {
|
||||
val fg = ResourcesCompat.getDrawable(resources, resId, null) ?: return null
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = TintedIconLayer(
|
||||
icon = fg,
|
||||
scale = 0.5f,
|
||||
),
|
||||
backgroundLayer = ColorLayer()
|
||||
)
|
||||
private fun getIconPackClockIcon(
|
||||
icon: ClockIcon,
|
||||
resources: Resources,
|
||||
): LauncherIcon? {
|
||||
var drawable = try {
|
||||
resources.getIdentifier(icon.drawable, "drawable", icon.iconPack).takeIf { it != 0 }
|
||||
?.let { ResourcesCompat.getDrawable(resources, it, null) }
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
null
|
||||
} ?: return null
|
||||
|
||||
private fun getThemedClockIcon(resources: Resources, resId: Int): LauncherIcon? {
|
||||
try {
|
||||
val array = resources.obtainTypedArrayOrNull(resId) ?: return null
|
||||
var i = 0
|
||||
var drawable: LayerDrawable? = null
|
||||
var minuteIndex: Int? = null
|
||||
var defaultMinute = 0
|
||||
var hourIndex: Int? = null
|
||||
var defaultHour = 0
|
||||
while (i < array.length()) {
|
||||
when (array.getString(i)) {
|
||||
"com.android.launcher3.LEVEL_PER_TICK_ICON_ROUND" -> {
|
||||
i++
|
||||
drawable = array.getDrawable(i) as? LayerDrawable
|
||||
}
|
||||
val background = (drawable as? AdaptiveIconDrawable)?.background
|
||||
val foreground = (drawable as? AdaptiveIconDrawable)?.foreground ?: drawable
|
||||
|
||||
"com.android.launcher3.HOUR_LAYER_INDEX" -> {
|
||||
i++
|
||||
hourIndex = array.getInt(i, -1).takeIf { it != -1 }
|
||||
}
|
||||
if (foreground !is LayerDrawable) return null
|
||||
|
||||
"com.android.launcher3.MINUTE_LAYER_INDEX" -> {
|
||||
i++
|
||||
minuteIndex = array.getInt(i, -1).takeIf { it != -1 }
|
||||
}
|
||||
|
||||
"com.android.launcher3.DEFAULT_HOUR" -> {
|
||||
i++
|
||||
defaultHour = array.getInt(i, 0)
|
||||
}
|
||||
|
||||
"com.android.launcher3.DEFAULT_MINUTE" -> {
|
||||
i++
|
||||
defaultMinute = array.getInt(i, 0)
|
||||
}
|
||||
val layers = (0 until foreground.numberOfLayers).map {
|
||||
val drw = foreground.getDrawable(it)
|
||||
ClockSublayer(
|
||||
drawable = drw,
|
||||
role = when (it) {
|
||||
icon.config.hourLayer -> ClockSublayerRole.Hour
|
||||
icon.config.minuteLayer -> ClockSublayerRole.Minute
|
||||
icon.config.secondLayer -> ClockSublayerRole.Second
|
||||
else -> ClockSublayerRole.Static
|
||||
}
|
||||
i++
|
||||
}
|
||||
if (drawable != null && minuteIndex != null && hourIndex != null) {
|
||||
)
|
||||
}
|
||||
|
||||
return StaticLauncherIcon(
|
||||
return when {
|
||||
icon.themed && drawable is AdaptiveIconDrawable -> {
|
||||
StaticLauncherIcon(
|
||||
foregroundLayer = TintedClockLayer(
|
||||
sublayers = (0 until drawable.numberOfLayers).map {
|
||||
val drw = drawable.getDrawable(it)
|
||||
if (drw is RotateDrawable) {
|
||||
drw.level = when (it) {
|
||||
hourIndex -> {
|
||||
(12 - defaultHour) * 60
|
||||
}
|
||||
|
||||
minuteIndex -> {
|
||||
(60 - defaultMinute)
|
||||
}
|
||||
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
ClockSublayer(
|
||||
drawable = drw,
|
||||
role = when (it) {
|
||||
hourIndex -> ClockSublayerRole.Hour
|
||||
minuteIndex -> ClockSublayerRole.Minute
|
||||
else -> ClockSublayerRole.Static
|
||||
}
|
||||
)
|
||||
},
|
||||
defaultHour = icon.config.defaultHour,
|
||||
defaultMinute = icon.config.defaultMinute,
|
||||
defaultSecond = icon.config.defaultSecond,
|
||||
sublayers = layers,
|
||||
scale = 1.5f,
|
||||
),
|
||||
backgroundLayer = ColorLayer()
|
||||
backgroundLayer = ColorLayer(),
|
||||
)
|
||||
}
|
||||
icon.themed -> {
|
||||
StaticLauncherIcon(
|
||||
foregroundLayer = TintedClockLayer(
|
||||
defaultHour = icon.config.defaultHour,
|
||||
defaultMinute = icon.config.defaultMinute,
|
||||
defaultSecond = icon.config.defaultSecond,
|
||||
sublayers = layers,
|
||||
scale = 1f,
|
||||
),
|
||||
backgroundLayer = ColorLayer(),
|
||||
)
|
||||
}
|
||||
drawable is AdaptiveIconDrawable -> {
|
||||
StaticLauncherIcon(
|
||||
foregroundLayer = ClockLayer(
|
||||
defaultHour = icon.config.defaultHour,
|
||||
defaultMinute = icon.config.defaultMinute,
|
||||
defaultSecond = icon.config.defaultSecond,
|
||||
sublayers = layers,
|
||||
scale = 1.5f,
|
||||
),
|
||||
backgroundLayer = StaticIconLayer(
|
||||
icon = background!!,
|
||||
scale = 1.5f
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
StaticLauncherIcon(
|
||||
foregroundLayer = ClockLayer(
|
||||
defaultHour = icon.config.defaultHour,
|
||||
defaultMinute = icon.config.defaultMinute,
|
||||
defaultSecond = icon.config.defaultSecond,
|
||||
sublayers = layers,
|
||||
scale = 1f,
|
||||
),
|
||||
backgroundLayer = TransparentLayer,
|
||||
)
|
||||
}
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getThemedCalendarIcon(
|
||||
resources: Resources,
|
||||
resId: Int,
|
||||
iconProviderPackage: String
|
||||
): LauncherIcon? {
|
||||
try {
|
||||
val array = resources.obtainTypedArrayOrNull(resId) ?: return null
|
||||
if (array.length() != 31) return null
|
||||
|
||||
return ThemedDynamicCalendarIcon(
|
||||
resources = resources,
|
||||
resourceIds = IntArray(31) {
|
||||
array.getResourceId(it, 0).takeIf { it != 0 } ?: return null
|
||||
},
|
||||
)
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun searchIconPackIcon(query: String, iconPack: IconPack?): List<IconPackIcon> {
|
||||
suspend fun searchIconPackIcon(query: String, iconPack: IconPack?): List<IconPackAppIcon> {
|
||||
val iconDao = appDatabase.iconDao()
|
||||
val drawableQuery = query.replace(" ", "_").lowercase()
|
||||
return iconDao.searchIconPackIcons(
|
||||
@ -435,17 +414,11 @@ class IconPackManager(
|
||||
componentQuery = "%$query%",
|
||||
nameQuery = "%$query%",
|
||||
iconPack = iconPack?.packageName,
|
||||
).map {
|
||||
IconPackIcon(it)
|
||||
).mapNotNull {
|
||||
IconPackAppIcon(it)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun searchThemedIcons(query: String): List<IconPackIcon> {
|
||||
val iconDao = appDatabase.iconDao()
|
||||
return iconDao.searchGreyscaleIcons("%$query%").map {
|
||||
IconPackIcon(it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package de.mm20.launcher2.icons
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
@ -24,7 +25,6 @@ import de.mm20.launcher2.icons.providers.IconPackIconProvider
|
||||
import de.mm20.launcher2.icons.providers.IconProvider
|
||||
import de.mm20.launcher2.icons.providers.PlaceholderIconProvider
|
||||
import de.mm20.launcher2.icons.providers.SystemIconProvider
|
||||
import de.mm20.launcher2.icons.providers.ThemedIconProvider
|
||||
import de.mm20.launcher2.icons.providers.ThemedPlaceholderIconProvider
|
||||
import de.mm20.launcher2.icons.providers.getFirstIcon
|
||||
import de.mm20.launcher2.icons.transformations.ForceThemedIconTransformation
|
||||
@ -39,11 +39,13 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.internal.ChannelFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@ -67,6 +69,11 @@ class IconRepository(
|
||||
private var iconProviders: MutableStateFlow<List<IconProvider>> = MutableStateFlow(listOf())
|
||||
private var placeholderProvider: IconProvider? = null
|
||||
|
||||
/**
|
||||
* Signal that installed icon packs have been updated. Force a reload of all icons.
|
||||
*/
|
||||
private val iconPacksUpdated = MutableSharedFlow<Unit>(1)
|
||||
|
||||
private var transformations: MutableStateFlow<List<LauncherIconTransformation>> =
|
||||
MutableStateFlow(
|
||||
listOf()
|
||||
@ -83,51 +90,52 @@ class IconRepository(
|
||||
addDataScheme("package")
|
||||
})
|
||||
|
||||
iconPacksUpdated.tryEmit(Unit)
|
||||
|
||||
scope.launch {
|
||||
dataStore.data.map { it.icons }.distinctUntilChanged().collectLatest { settings ->
|
||||
val placeholderProvider = if (settings.themedIcons) {
|
||||
ThemedPlaceholderIconProvider(context)
|
||||
} else {
|
||||
PlaceholderIconProvider(context)
|
||||
}
|
||||
val providers = mutableListOf<IconProvider>()
|
||||
|
||||
if (settings.iconPack.isNotBlank()) {
|
||||
val pack = iconPackManager.getIconPack(settings.iconPack)
|
||||
if (pack != null) {
|
||||
providers.add(
|
||||
IconPackIconProvider(
|
||||
context,
|
||||
pack,
|
||||
iconPackManager
|
||||
)
|
||||
)
|
||||
iconPacksUpdated.collectLatest {
|
||||
val placeholderProvider = if (settings.themedIcons) {
|
||||
ThemedPlaceholderIconProvider(context)
|
||||
} else {
|
||||
Log.w("MM20", "Icon pack ${settings.iconPack} not found")
|
||||
PlaceholderIconProvider(context)
|
||||
}
|
||||
}
|
||||
if (settings.themedIcons) {
|
||||
providers.add(ThemedIconProvider(iconPackManager))
|
||||
}
|
||||
providers.add(DynamicClockIconProvider(context, settings.themedIcons))
|
||||
providers.add(CalendarIconProvider(context, settings.themedIcons))
|
||||
if (!isAtLeastApiLevel(33)) {
|
||||
providers.add(CompatIconProvider(context, settings.themedIcons))
|
||||
}
|
||||
providers.add(SystemIconProvider(context, settings.themedIcons))
|
||||
providers.add(placeholderProvider)
|
||||
cache.evictAll()
|
||||
val providers = mutableListOf<IconProvider>()
|
||||
|
||||
val transformations = mutableListOf<LauncherIconTransformation>()
|
||||
if (settings.iconPack.isNotBlank()) {
|
||||
val pack = iconPackManager.getIconPack(settings.iconPack)
|
||||
if (pack != null) {
|
||||
providers.add(
|
||||
IconPackIconProvider(
|
||||
context,
|
||||
pack,
|
||||
iconPackManager
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Log.w("MM20", "Icon pack ${settings.iconPack} not found")
|
||||
}
|
||||
}
|
||||
providers.add(DynamicClockIconProvider(context, settings.themedIcons))
|
||||
providers.add(CalendarIconProvider(context, settings.themedIcons))
|
||||
if (!isAtLeastApiLevel(33)) {
|
||||
providers.add(CompatIconProvider(context, settings.themedIcons))
|
||||
}
|
||||
providers.add(SystemIconProvider(context, settings.themedIcons))
|
||||
providers.add(placeholderProvider)
|
||||
cache.evictAll()
|
||||
|
||||
if (settings.adaptify) transformations.add(LegacyToAdaptiveTransformation())
|
||||
if (settings.themedIcons && settings.forceThemed) transformations.add(
|
||||
ForceThemedIconTransformation()
|
||||
)
|
||||
val transformations = mutableListOf<LauncherIconTransformation>()
|
||||
|
||||
this@IconRepository.placeholderProvider = placeholderProvider
|
||||
iconProviders.value = providers
|
||||
this@IconRepository.transformations.value = transformations
|
||||
if (settings.adaptify) transformations.add(LegacyToAdaptiveTransformation())
|
||||
if (settings.themedIcons && settings.forceThemed) transformations.add(
|
||||
ForceThemedIconTransformation()
|
||||
)
|
||||
|
||||
this@IconRepository.placeholderProvider = placeholderProvider
|
||||
iconProviders.value = providers
|
||||
this@IconRepository.transformations.value = transformations
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -215,7 +223,9 @@ class IconRepository(
|
||||
|
||||
fun requestIconPackListUpdate() {
|
||||
scope.launch {
|
||||
iconPackManager.updateIconPacks()
|
||||
iconPackManager.updateIconPacks().also {
|
||||
if (it)iconPacksUpdated.tryEmit(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,24 +295,14 @@ class IconRepository(
|
||||
iconPackIcons.mapNotNull {
|
||||
CustomIconPackIcon(
|
||||
iconPackPackage = it.iconPack,
|
||||
iconComponentName = it.componentName?.flattenToString()
|
||||
?: return@mapNotNull null
|
||||
iconActivityName = it.activityName,
|
||||
iconPackageName = it.packageName,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
val themedIcon = iconPackManager.getGreyscaleIcon(searchable.`package`)
|
||||
if (themedIcon != null && themedIcon.componentName?.packageName != null) {
|
||||
providerOptions.add(
|
||||
CustomThemedIcon(
|
||||
iconPackageName = themedIcon.componentName.packageName,
|
||||
)
|
||||
)
|
||||
} else {
|
||||
transformationOptions.add(
|
||||
ForceThemedIcon
|
||||
)
|
||||
}
|
||||
transformationOptions.add(
|
||||
ForceThemedIcon
|
||||
)
|
||||
} else {
|
||||
transformationOptions.add(
|
||||
ForceThemedIcon
|
||||
@ -359,31 +359,18 @@ class IconRepository(
|
||||
suspend fun searchCustomIcons(query: String, iconPack: IconPack?): List<CustomIconWithPreview> {
|
||||
val transformations = this.transformations.first()
|
||||
val iconPackIcons = iconPackManager.searchIconPackIcon(query, iconPack).mapNotNull {
|
||||
val componentName = it.componentName ?: return@mapNotNull null
|
||||
|
||||
CustomIconWithPreview(
|
||||
customIcon = CustomIconPackIcon(
|
||||
iconPackPackage = it.iconPack,
|
||||
iconComponentName = componentName.flattenToString(),
|
||||
iconActivityName = it.activityName,
|
||||
iconPackageName = it.packageName,
|
||||
),
|
||||
preview = iconPackManager.getIcon(it.iconPack, componentName)
|
||||
preview = iconPackManager.getIcon(it.iconPack, it.packageName, it.activityName)
|
||||
?.transform(transformations) ?: return@mapNotNull null
|
||||
)
|
||||
}
|
||||
|
||||
val themedIcons = iconPackManager.searchThemedIcons(query).mapNotNull {
|
||||
val componentName = it.componentName ?: return@mapNotNull null
|
||||
|
||||
CustomIconWithPreview(
|
||||
customIcon = CustomThemedIcon(
|
||||
iconPackageName = componentName.packageName,
|
||||
),
|
||||
preview = iconPackManager.getThemedIcon(componentName.packageName)
|
||||
?.transform(transformations) ?: return@mapNotNull null
|
||||
)
|
||||
}
|
||||
|
||||
return iconPackIcons + themedIcons
|
||||
return iconPackIcons
|
||||
}
|
||||
|
||||
fun setCustomIcon(searchable: SavableSearchable, icon: CustomIcon?) {
|
||||
|
||||
@ -145,23 +145,6 @@ fun AdaptiveIconDrawableCompat.toLauncherIcon(
|
||||
if (clock != null && clockForeground != null) {
|
||||
val clockLayers = (0 until clockForeground.numberOfLayers).map {
|
||||
val drw = clockForeground.getDrawable(it)
|
||||
if (drw is RotateDrawable) {
|
||||
drw.level = when (it) {
|
||||
clock.hourLayer -> {
|
||||
(12 - clock.defaultHour) * 60
|
||||
}
|
||||
|
||||
clock.minuteLayer -> {
|
||||
(60 - clock.defaultMinute)
|
||||
}
|
||||
|
||||
clock.secondLayer -> {
|
||||
(60 - clock.defaultSecond) * 10
|
||||
}
|
||||
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
ClockSublayer(
|
||||
drawable = drw,
|
||||
role = when (it) {
|
||||
@ -175,6 +158,9 @@ fun AdaptiveIconDrawableCompat.toLauncherIcon(
|
||||
if (themed) {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = TintedClockLayer(
|
||||
defaultHour = clock.defaultHour,
|
||||
defaultMinute = clock.defaultMinute,
|
||||
defaultSecond = clock.defaultSecond,
|
||||
sublayers = clockLayers,
|
||||
scale = 1.5f,
|
||||
),
|
||||
@ -183,6 +169,9 @@ fun AdaptiveIconDrawableCompat.toLauncherIcon(
|
||||
}
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = ClockLayer(
|
||||
defaultHour = clock.defaultHour,
|
||||
defaultMinute = clock.defaultMinute,
|
||||
defaultSecond = clock.defaultSecond,
|
||||
sublayers = clockLayers,
|
||||
scale = 1.5f,
|
||||
),
|
||||
|
||||
@ -0,0 +1,197 @@
|
||||
package de.mm20.launcher2.icons.loaders
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.XmlResourceParser
|
||||
import android.util.Log
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.database.AppDatabase
|
||||
import de.mm20.launcher2.icons.AppIcon
|
||||
import de.mm20.launcher2.icons.CalendarIcon
|
||||
import de.mm20.launcher2.icons.IconBack
|
||||
import de.mm20.launcher2.icons.IconMask
|
||||
import de.mm20.launcher2.icons.IconPack
|
||||
import de.mm20.launcher2.icons.IconUpon
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
import org.xmlpull.v1.XmlPullParserFactory
|
||||
import java.io.IOException
|
||||
import java.io.Reader
|
||||
|
||||
class AppFilterIconPackInstaller(
|
||||
private val context: Context,
|
||||
database: AppDatabase,
|
||||
) : IconPackInstaller(database) {
|
||||
override suspend fun IconPackInstallerScope.buildIconPack(iconPack: IconPack) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val pkgName = iconPack.packageName
|
||||
|
||||
try {
|
||||
val res = context.packageManager.getResourcesForApplication(pkgName)
|
||||
val parser: XmlPullParser
|
||||
var inStream: Reader? = null
|
||||
val xmlId = res.getIdentifier("appfilter", "xml", pkgName)
|
||||
val rawId = res.getIdentifier("appfilter", "raw", pkgName)
|
||||
parser = when {
|
||||
xmlId != 0 -> res.getXml(xmlId)
|
||||
rawId != 0 -> {
|
||||
inStream = res.openRawResource(rawId).reader()
|
||||
XmlPullParserFactory.newInstance().newPullParser().apply {
|
||||
setInput(inStream)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
val iconPackContext = context.createPackageContext(
|
||||
pkgName,
|
||||
Context.CONTEXT_IGNORE_SECURITY
|
||||
)
|
||||
inStream = try {
|
||||
iconPackContext.assets.open("appfilter.xml").reader()
|
||||
} catch (e: IOException) {
|
||||
CrashReporter.logException(e)
|
||||
Log.e(
|
||||
"MM20",
|
||||
"appfilter.xml not found in $pkgName. Searched locations: res/xml/appfilter.xml, res/raw/appfilter.xml, assets/appfilter.xml"
|
||||
)
|
||||
return@withContext
|
||||
}
|
||||
XmlPullParserFactory.newInstance().newPullParser().apply {
|
||||
setInput(inStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop@ while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG) continue
|
||||
when (parser.name) {
|
||||
"item" -> {
|
||||
val component = parser.getAttributeValue(null, "component")
|
||||
?: continue@loop
|
||||
val drawable = parser.getAttributeValue(null, "drawable")
|
||||
?: continue@loop
|
||||
if (component.length <= 14) continue@loop
|
||||
val componentName = ComponentName.unflattenFromString(
|
||||
component.substring(
|
||||
14,
|
||||
component.lastIndex
|
||||
)
|
||||
)
|
||||
?: continue@loop
|
||||
|
||||
val name = parser.getAttributeValue(null, "name")
|
||||
|
||||
val icon = AppIcon(
|
||||
packageName = componentName.packageName,
|
||||
activityName = componentName.shortClassName,
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
name = name,
|
||||
themed = iconPack.themed,
|
||||
)
|
||||
addIcon(icon)
|
||||
}
|
||||
|
||||
"calendar" -> {
|
||||
val component = parser.getAttributeValue(null, "component")
|
||||
?: continue@loop
|
||||
val drawable = parser.getAttributeValue(null, "prefix") ?: continue@loop
|
||||
if (component.length < 14) continue@loop
|
||||
val componentName = ComponentName.unflattenFromString(
|
||||
component.substring(
|
||||
14,
|
||||
component.lastIndex
|
||||
)
|
||||
)
|
||||
?: continue@loop
|
||||
|
||||
val name = parser.getAttributeValue(null, "name")
|
||||
|
||||
val icon = CalendarIcon(
|
||||
packageName = componentName.packageName,
|
||||
activityName = componentName.shortClassName,
|
||||
drawables = (0..31).map { "$drawable$it" },
|
||||
iconPack = pkgName,
|
||||
themed = iconPack.themed,
|
||||
name = name,
|
||||
)
|
||||
addIcon(icon)
|
||||
}
|
||||
|
||||
"iconback" -> {
|
||||
for (i in 0 until parser.attributeCount) {
|
||||
if (parser.getAttributeName(i).startsWith("img")) {
|
||||
val drawable = parser.getAttributeValue(i)
|
||||
val icon = IconBack(
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
)
|
||||
addIcon(icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"iconupon" -> {
|
||||
for (i in 0 until parser.attributeCount) {
|
||||
if (parser.getAttributeName(i).startsWith("img")) {
|
||||
val drawable = parser.getAttributeValue(i)
|
||||
val icon = IconUpon(
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
)
|
||||
addIcon(icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"iconmask" -> {
|
||||
for (i in 0 until parser.attributeCount) {
|
||||
if (parser.getAttributeName(i).startsWith("img")) {
|
||||
val drawable = parser.getAttributeValue(i)
|
||||
val icon = IconMask(
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
)
|
||||
addIcon(icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"scale" -> {
|
||||
val scale = parser.getAttributeValue(null, "factor")?.toFloatOrNull()
|
||||
?: continue@loop
|
||||
updatePackInfo { it.copy(scale = scale) }
|
||||
}
|
||||
}
|
||||
}
|
||||
(parser as? XmlResourceParser)?.close()
|
||||
inStream?.close()
|
||||
|
||||
Log.d("MM20", "Icon pack $pkgName has been installed successfully")
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Log.e("MM20", "Could not install icon pack $pkgName: package not found.")
|
||||
} catch (e: XmlPullParserException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getInstalledIconPacks(): List<IconPack> {
|
||||
val packs = mutableListOf<IconPack>()
|
||||
val pm = context.packageManager
|
||||
var intent = Intent("app.lawnchair.icons.THEMED_ICON")
|
||||
val themedPacks = pm.queryIntentActivities(intent, 0)
|
||||
packs.addAll(themedPacks.map { IconPack(context, it, true) })
|
||||
intent = Intent("org.adw.ActivityStarter.THEMES")
|
||||
val adwPacks = pm.queryIntentActivities(intent, 0)
|
||||
packs.addAll(adwPacks.map { IconPack(context, it, false) })
|
||||
intent = Intent("com.novalauncher.THEME")
|
||||
val novaPacks = pm.queryIntentActivities(intent, 0)
|
||||
packs.addAll(novaPacks.map { IconPack(context, it, false) })
|
||||
return packs.distinctBy { it.packageName }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,178 @@
|
||||
package de.mm20.launcher2.icons.loaders
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Resources
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.util.Log
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.database.AppDatabase
|
||||
import de.mm20.launcher2.icons.AppIcon
|
||||
import de.mm20.launcher2.icons.CalendarIcon
|
||||
import de.mm20.launcher2.icons.ClockIcon
|
||||
import de.mm20.launcher2.icons.IconPack
|
||||
import de.mm20.launcher2.icons.compat.ClockIconConfig
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
|
||||
class GrayscaleMapIconPackInstaller(
|
||||
private val context: Context,
|
||||
database: AppDatabase,
|
||||
) : IconPackInstaller(database) {
|
||||
private val SUPPORTED_GRAYSCALE_MAP_PROVIDERS = arrayOf(
|
||||
"app.lawnchair.lawnicons", // Lawnicons
|
||||
"de.mm20.launcher2.themedicons",
|
||||
"de.kvaesitso.icons",
|
||||
)
|
||||
|
||||
override suspend fun IconPackInstallerScope.buildIconPack(iconPack: IconPack) {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val packageName = iconPack.packageName
|
||||
val resources = context.packageManager.getResourcesForApplication(packageName)
|
||||
val resId = resources.getIdentifier("grayscale_icon_map", "xml", packageName)
|
||||
if (resId == 0) {
|
||||
Log.d("MM20", "Could not find grayscale_icon_map.xml in $packageName")
|
||||
return@withContext
|
||||
}
|
||||
val parser = resources.getXml(resId)
|
||||
loop@ while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG) continue
|
||||
if (parser.name == "icon") {
|
||||
val pkg = parser.getAttributeValue(null, "package") ?: continue@loop
|
||||
val drawableRes =
|
||||
parser.getAttributeResourceValue(null, "drawable", 0)
|
||||
|
||||
val type = try {
|
||||
resources.getResourceTypeName(drawableRes)
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
continue@loop
|
||||
}
|
||||
|
||||
if (type == "drawable") {
|
||||
val drawableName =
|
||||
resources.getResourceEntryNameOrNull(drawableRes) ?: continue@loop
|
||||
val icon = AppIcon(
|
||||
drawable = drawableName,
|
||||
packageName = pkg,
|
||||
activityName = null,
|
||||
iconPack = packageName,
|
||||
name = null,
|
||||
themed = true
|
||||
)
|
||||
addIcon(icon)
|
||||
} else if (type == "array") {
|
||||
val array = resources.obtainTypedArray(drawableRes)
|
||||
if (array.length() == 31) {
|
||||
val drawables = mutableListOf<String>()
|
||||
for (i in 0 until 31) {
|
||||
val res = array.getResourceId(i, 0)
|
||||
if (res == 0) break
|
||||
val drawableName =
|
||||
resources.getResourceEntryNameOrNull(res) ?: break
|
||||
drawables.add(drawableName)
|
||||
}
|
||||
if (drawables.size == 31) {
|
||||
addIcon(
|
||||
CalendarIcon(
|
||||
drawables = drawables,
|
||||
packageName = pkg,
|
||||
iconPack = packageName,
|
||||
themed = true
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
var i = 0
|
||||
var drawable: LayerDrawable? = null
|
||||
var drawableName: String? = null
|
||||
var minuteIndex: Int? = null
|
||||
var defaultMinute = 0
|
||||
var hourIndex: Int? = null
|
||||
var defaultHour = 0
|
||||
while (i < array.length()) {
|
||||
when (array.getString(i)) {
|
||||
"com.android.launcher3.LEVEL_PER_TICK_ICON_ROUND" -> {
|
||||
i++
|
||||
drawable = array.getDrawable(i) as? LayerDrawable
|
||||
drawableName =
|
||||
array.getResourceId(i, 0).takeIf { it != 0 }
|
||||
?.let { resources.getResourceEntryNameOrNull(it) }
|
||||
}
|
||||
|
||||
"com.android.launcher3.HOUR_LAYER_INDEX" -> {
|
||||
i++
|
||||
hourIndex = array.getInt(i, -1).takeIf { it != -1 }
|
||||
}
|
||||
|
||||
"com.android.launcher3.MINUTE_LAYER_INDEX" -> {
|
||||
i++
|
||||
minuteIndex = array.getInt(i, -1).takeIf { it != -1 }
|
||||
}
|
||||
|
||||
"com.android.launcher3.DEFAULT_HOUR" -> {
|
||||
i++
|
||||
defaultHour = array.getInt(i, 0)
|
||||
}
|
||||
|
||||
"com.android.launcher3.DEFAULT_MINUTE" -> {
|
||||
i++
|
||||
defaultMinute = array.getInt(i, 0)
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
if (drawable != null && drawableName != null && minuteIndex != null && hourIndex != null) {
|
||||
addIcon(
|
||||
ClockIcon(
|
||||
drawable = drawableName,
|
||||
packageName = pkg,
|
||||
config = ClockIconConfig(
|
||||
hourLayer = hourIndex,
|
||||
minuteLayer = minuteIndex,
|
||||
defaultHour = defaultHour,
|
||||
defaultMinute = defaultMinute,
|
||||
defaultSecond = 0,
|
||||
secondLayer = -1,
|
||||
),
|
||||
iconPack = packageName,
|
||||
themed = true,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
array.recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getInstalledIconPacks(): List<IconPack> {
|
||||
val pm = context.packageManager
|
||||
return SUPPORTED_GRAYSCALE_MAP_PROVIDERS.mapNotNull {
|
||||
try {
|
||||
val packageInfo = pm.getPackageInfo(it, 0)
|
||||
IconPack(
|
||||
context = context,
|
||||
packageInfo = packageInfo,
|
||||
themed = true,
|
||||
)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Resources.getResourceEntryNameOrNull(res: Int): String? {
|
||||
return try {
|
||||
getResourceEntryName(res)
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
@ -1,88 +0,0 @@
|
||||
package de.mm20.launcher2.icons.loaders
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import de.mm20.launcher2.database.AppDatabase
|
||||
import de.mm20.launcher2.icons.IconPackIcon
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
|
||||
class GrayscaleMapInstaller(
|
||||
private val context: Context,
|
||||
private val database: AppDatabase,
|
||||
) {
|
||||
private val SUPPORTED_GRAYSCALE_MAP_PROVIDERS = arrayOf(
|
||||
"com.google.android.apps.nexuslauncher", // Pixel Launcher
|
||||
"app.lawnchair.lawnicons", // Lawnicons
|
||||
"app.lawnchair", // Lawnchair
|
||||
"de.mm20.launcher2.themedicons",
|
||||
"de.kvaesitso.icons",
|
||||
)
|
||||
|
||||
fun installIcons() {
|
||||
val grayscaleProviders = loadInstalledGreyscaleProviders(context)
|
||||
|
||||
val dao = database.iconDao()
|
||||
|
||||
grayscaleProviders.forEach { installGrayscaleIconMap(it) }
|
||||
|
||||
dao.deleteAllGrayscaleIconsExcept(grayscaleProviders)
|
||||
}
|
||||
|
||||
private fun loadInstalledGreyscaleProviders(context: Context): List<String> {
|
||||
val pm = context.packageManager
|
||||
return SUPPORTED_GRAYSCALE_MAP_PROVIDERS.filter {
|
||||
try {
|
||||
pm.getPackageInfo(it, 0)
|
||||
true
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun installGrayscaleIconMap(packageName: String) {
|
||||
database.runInTransaction {
|
||||
val iconDao = database.iconDao()
|
||||
try {
|
||||
val resources = context.packageManager.getResourcesForApplication(packageName)
|
||||
val resId = resources.getIdentifier("grayscale_icon_map", "xml", packageName)
|
||||
iconDao.deleteGrayscaleIcons(packageName)
|
||||
if (resId == 0) {
|
||||
return@runInTransaction
|
||||
}
|
||||
val icons = mutableListOf<IconPackIcon>()
|
||||
val parser = resources.getXml(resId)
|
||||
loop@ while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG) continue
|
||||
when (parser.name) {
|
||||
"icon" -> {
|
||||
val drawable =
|
||||
parser.getAttributeResourceValue(null, "drawable", 0).toString()
|
||||
val pkg = parser.getAttributeValue(null, "package")
|
||||
val componentName = ComponentName(pkg, pkg)
|
||||
val icon = IconPackIcon(
|
||||
drawable = drawable,
|
||||
componentName = componentName,
|
||||
iconPack = packageName,
|
||||
type = "greyscale_icon"
|
||||
)
|
||||
icons.add(icon)
|
||||
}
|
||||
}
|
||||
if (icons.size >= 100) {
|
||||
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||
icons.clear()
|
||||
}
|
||||
}
|
||||
if (icons.isNotEmpty()) {
|
||||
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||
}
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
iconDao.deleteGrayscaleIcons(packageName)
|
||||
return@runInTransaction
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,233 +1,53 @@
|
||||
package de.mm20.launcher2.icons.loaders
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.XmlResourceParser
|
||||
import android.util.Log
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import androidx.room.withTransaction
|
||||
import de.mm20.launcher2.database.AppDatabase
|
||||
import de.mm20.launcher2.icons.BuildConfig
|
||||
import de.mm20.launcher2.icons.IconPack
|
||||
import de.mm20.launcher2.icons.IconPackIcon
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
import org.xmlpull.v1.XmlPullParserFactory
|
||||
import java.io.IOException
|
||||
import java.io.Reader
|
||||
import de.mm20.launcher2.icons.IconPackComponent
|
||||
|
||||
class IconPackInstaller(
|
||||
private val context: Context,
|
||||
abstract class IconPackInstaller(
|
||||
private val database: AppDatabase,
|
||||
) {
|
||||
|
||||
fun installIcons() {
|
||||
val packs = loadInstalledPacks(context)
|
||||
val iconDao = database.iconDao()
|
||||
|
||||
for (pack in packs) {
|
||||
try {
|
||||
installIconPack(pack)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
iconDao.uninstallIconPacksExcept(
|
||||
packs.map { it.packageName }.toList()
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadInstalledPacks(context: Context): List<IconPack> {
|
||||
val packs = mutableListOf<IconPack>()
|
||||
val pm = context.packageManager
|
||||
var intent = Intent("app.lawnchair.icons.THEMED_ICON")
|
||||
val themedPacks = pm.queryIntentActivities(intent, 0)
|
||||
packs.addAll(themedPacks.map { IconPack(context, it, true) })
|
||||
intent = Intent("org.adw.ActivityStarter.THEMES")
|
||||
val adwPacks = pm.queryIntentActivities(intent, 0)
|
||||
packs.addAll(adwPacks.map { IconPack(context, it, false) })
|
||||
intent = Intent("com.novalauncher.THEME")
|
||||
val novaPacks = pm.queryIntentActivities(intent, 0)
|
||||
packs.addAll(novaPacks.map { IconPack(context, it, false) })
|
||||
return packs.distinctBy { it.packageName }
|
||||
}
|
||||
|
||||
private fun installIconPack(iconPack: IconPack) {
|
||||
val pkgName = iconPack.packageName
|
||||
|
||||
val icons = mutableListOf<IconPackIcon>()
|
||||
database.runInTransaction {
|
||||
try {
|
||||
val res = context.packageManager.getResourcesForApplication(pkgName)
|
||||
val parser: XmlPullParser
|
||||
var inStream: Reader? = null
|
||||
val xmlId = res.getIdentifier("appfilter", "xml", pkgName)
|
||||
val rawId = res.getIdentifier("appfilter", "raw", pkgName)
|
||||
parser = when {
|
||||
xmlId != 0 -> res.getXml(xmlId)
|
||||
rawId != 0 -> {
|
||||
inStream = res.openRawResource(rawId).reader()
|
||||
XmlPullParserFactory.newInstance().newPullParser().apply {
|
||||
setInput(inStream)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
val iconPackContext = context.createPackageContext(
|
||||
pkgName,
|
||||
Context.CONTEXT_IGNORE_SECURITY
|
||||
)
|
||||
inStream = try {
|
||||
iconPackContext.assets.open("appfilter.xml").reader()
|
||||
} catch (e: IOException) {
|
||||
CrashReporter.logException(e)
|
||||
Log.e(
|
||||
"MM20",
|
||||
"appfilter.xml not found in $pkgName. Searched locations: res/xml/appfilter.xml, res/raw/appfilter.xml, assets/appfilter.xml"
|
||||
)
|
||||
return@runInTransaction
|
||||
}
|
||||
XmlPullParserFactory.newInstance().newPullParser().apply {
|
||||
setInput(inStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
val iconDao = database.iconDao()
|
||||
|
||||
iconDao.deleteIconPack(iconPack.toDatabaseEntity())
|
||||
iconDao.deleteIcons(iconPack.packageName)
|
||||
|
||||
loop@ while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG) continue
|
||||
when (parser.name) {
|
||||
"item" -> {
|
||||
val component = parser.getAttributeValue(null, "component")
|
||||
?: continue@loop
|
||||
val drawable = parser.getAttributeValue(null, "drawable")
|
||||
?: continue@loop
|
||||
if (component.length <= 14) continue@loop
|
||||
val componentName = ComponentName.unflattenFromString(
|
||||
component.substring(
|
||||
14,
|
||||
component.lastIndex
|
||||
)
|
||||
)
|
||||
?: continue@loop
|
||||
|
||||
val name = parser.getAttributeValue(null, "name")
|
||||
|
||||
val icon = IconPackIcon(
|
||||
componentName = componentName,
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
name = name,
|
||||
themed = iconPack.themed,
|
||||
type = "app"
|
||||
)
|
||||
icons.add(icon)
|
||||
}
|
||||
|
||||
"calendar" -> {
|
||||
val component = parser.getAttributeValue(null, "component")
|
||||
?: continue@loop
|
||||
val drawable = parser.getAttributeValue(null, "prefix") ?: continue@loop
|
||||
if (component.length < 14) continue@loop
|
||||
val componentName = ComponentName.unflattenFromString(
|
||||
component.substring(
|
||||
14,
|
||||
component.lastIndex
|
||||
)
|
||||
)
|
||||
?: continue@loop
|
||||
|
||||
val name = parser.getAttributeValue(null, "name")
|
||||
|
||||
val icon = IconPackIcon(
|
||||
componentName = componentName,
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
type = "calendar",
|
||||
themed = iconPack.themed,
|
||||
name = name,
|
||||
)
|
||||
icons.add(icon)
|
||||
}
|
||||
|
||||
"iconback" -> {
|
||||
for (i in 0 until parser.attributeCount) {
|
||||
if (parser.getAttributeName(i).startsWith("img")) {
|
||||
val drawable = parser.getAttributeValue(i)
|
||||
val icon = IconPackIcon(
|
||||
componentName = null,
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
type = "iconback"
|
||||
)
|
||||
icons.add(icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"iconupon" -> {
|
||||
for (i in 0 until parser.attributeCount) {
|
||||
if (parser.getAttributeName(i).startsWith("img")) {
|
||||
val drawable = parser.getAttributeValue(i)
|
||||
val icon = IconPackIcon(
|
||||
componentName = null,
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
type = "iconupon"
|
||||
)
|
||||
icons.add(icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"iconmask" -> {
|
||||
for (i in 0 until parser.attributeCount) {
|
||||
if (parser.getAttributeName(i).startsWith("img")) {
|
||||
val drawable = parser.getAttributeValue(i)
|
||||
val icon = IconPackIcon(
|
||||
componentName = null,
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
type = "iconmask"
|
||||
)
|
||||
icons.add(icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"scale" -> {
|
||||
val scale = parser.getAttributeValue(null, "factor")?.toFloatOrNull()
|
||||
?: continue@loop
|
||||
iconPack.scale = scale
|
||||
}
|
||||
}
|
||||
suspend fun install(iconPack: IconPack) {
|
||||
var pack = iconPack
|
||||
val dao = database.iconDao()
|
||||
database.withTransaction {
|
||||
dao.deleteIconPack(iconPack.toDatabaseEntity())
|
||||
dao.deleteIcons(iconPack.packageName)
|
||||
val icons = mutableListOf<IconPackComponent>()
|
||||
val installerScope = object: IconPackInstallerScope {
|
||||
override suspend fun addIcon(icon: IconPackComponent) {
|
||||
icons.add(icon)
|
||||
if (icons.size >= 100) {
|
||||
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||
dao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||
icons.clear()
|
||||
}
|
||||
}
|
||||
|
||||
if (icons.isNotEmpty()) {
|
||||
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||
override suspend fun updatePackInfo(update: (IconPack) -> IconPack) {
|
||||
pack = update(iconPack)
|
||||
}
|
||||
iconDao.installIconPack(iconPack.toDatabaseEntity())
|
||||
|
||||
(parser as? XmlResourceParser)?.close()
|
||||
inStream?.close()
|
||||
|
||||
Log.d("MM20", "Icon pack has been installed successfully")
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Log.e("MM20", "Could not install icon pack $pkgName: package not found.")
|
||||
} catch (e: XmlPullParserException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
|
||||
installerScope.buildIconPack(iconPack)
|
||||
if (icons.isNotEmpty()) dao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||
dao.installIconPack(pack.toDatabaseEntity())
|
||||
}
|
||||
}
|
||||
|
||||
abstract suspend fun IconPackInstallerScope.buildIconPack(iconPack: IconPack)
|
||||
|
||||
abstract fun getInstalledIconPacks(): List<IconPack>
|
||||
|
||||
suspend fun isInstalledAndUpToDate(iconPack: IconPack): Boolean {
|
||||
if (BuildConfig.DEBUG) return false
|
||||
val dao = database.iconDao()
|
||||
val installed = dao.getIconPack(iconPack.packageName)?.let { IconPack(it) } ?: return false
|
||||
return installed.version == iconPack.version
|
||||
}
|
||||
}
|
||||
|
||||
interface IconPackInstallerScope {
|
||||
suspend fun addIcon(icon: IconPackComponent)
|
||||
suspend fun updatePackInfo(update: (IconPack) -> IconPack)
|
||||
}
|
||||
@ -13,7 +13,8 @@ class CustomIconPackIconProvider(
|
||||
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
|
||||
return iconPackManager.getIcon(
|
||||
customIcon.iconPackPackage,
|
||||
ComponentName.unflattenFromString(customIcon.iconComponentName) ?: return null
|
||||
customIcon.iconPackageName,
|
||||
customIcon.iconActivityName,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,6 @@ class CustomThemedIconProvider(
|
||||
private val iconPackManager: IconPackManager,
|
||||
): IconProvider {
|
||||
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
|
||||
return iconPackManager.getThemedIcon(customIcon.iconPackageName)
|
||||
return null //iconPackManager.getThemedIcon(customIcon.iconPackageName)
|
||||
}
|
||||
}
|
||||
@ -16,8 +16,7 @@ class IconPackIconProvider(
|
||||
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
|
||||
if (searchable !is LauncherApp) return null
|
||||
|
||||
val component = ComponentName(searchable.`package`, searchable.activity)
|
||||
return iconPackManager.getIcon(iconPack.packageName, component)
|
||||
return iconPackManager.getIcon(iconPack.packageName, searchable.`package`, searchable.activity)
|
||||
?: iconPackManager.generateIcon(
|
||||
context,
|
||||
iconPack.packageName,
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
package de.mm20.launcher2.icons.providers
|
||||
|
||||
import de.mm20.launcher2.icons.*
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.data.LauncherApp
|
||||
|
||||
internal class ThemedIconProvider(
|
||||
private val iconPackManager: IconPackManager,
|
||||
) : IconProvider {
|
||||
|
||||
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
|
||||
if (searchable !is LauncherApp) return null
|
||||
return iconPackManager.getThemedIcon(searchable.`package`)
|
||||
}
|
||||
}
|
||||
@ -22,6 +22,9 @@ internal class ThemedPlaceholderIconProvider(
|
||||
is ClockLayer -> TintedClockLayer(
|
||||
scale = layer.scale,
|
||||
color = 0,
|
||||
defaultHour = layer.defaultHour,
|
||||
defaultMinute = layer.defaultMinute,
|
||||
defaultSecond = layer.defaultSecond,
|
||||
sublayers = layer.sublayers,
|
||||
)
|
||||
is ColorLayer -> layer.copy(color = 0)
|
||||
|
||||
@ -14,6 +14,9 @@ internal class ForceThemedIconTransformation : LauncherIconTransformation {
|
||||
return when(layer) {
|
||||
is ClockLayer -> TintedClockLayer(
|
||||
scale = layer.scale,
|
||||
defaultHour = layer.defaultHour,
|
||||
defaultMinute = layer.defaultMinute,
|
||||
defaultSecond = layer.defaultSecond,
|
||||
sublayers = layer.sublayers,
|
||||
)
|
||||
is ColorLayer -> layer.copy(color = 0)
|
||||
|
||||
@ -138,7 +138,7 @@ dependencyResolutionManagement {
|
||||
library("androidx.datastore", "androidx.datastore", "datastore")
|
||||
.version("1.0.0")
|
||||
|
||||
version("androidx.room", "2.5.0-alpha03")
|
||||
version("androidx.room", "2.5.0")
|
||||
library("androidx.roomruntime", "androidx.room", "room-runtime")
|
||||
.versionRef("androidx.room")
|
||||
library("androidx.roomcompiler", "androidx.room", "room-compiler")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user