Restructure / cleanup / improve icon pack handling
This commit is contained in:
parent
96f69356c4
commit
7168169e35
@ -237,13 +237,23 @@ private fun IconLayer(
|
|||||||
) {
|
) {
|
||||||
when (layer) {
|
when (layer) {
|
||||||
is ClockLayer -> {
|
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 -> {
|
is TintedClockLayer -> {
|
||||||
ClockLayer(
|
ClockLayer(
|
||||||
layer.sublayers,
|
layer.sublayers,
|
||||||
scale = layer.scale,
|
scale = layer.scale,
|
||||||
|
defaultSecond = layer.defaultSecond,
|
||||||
|
defaultMinute = layer.defaultMinute,
|
||||||
|
defaultHour = layer.defaultHour,
|
||||||
tintColor = if (layer.color == 0) defaultTintColor
|
tintColor = if (layer.color == 0) defaultTintColor
|
||||||
else Color(getTone(layer.color, colorTone))
|
else Color(getTone(layer.color, colorTone))
|
||||||
)
|
)
|
||||||
@ -323,6 +333,9 @@ private fun getTone(argb: Int, tone: Int): Int {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun ClockLayer(
|
private fun ClockLayer(
|
||||||
sublayers: List<ClockSublayer>,
|
sublayers: List<ClockSublayer>,
|
||||||
|
defaultMinute: Int,
|
||||||
|
defaultHour: Int,
|
||||||
|
defaultSecond: Int,
|
||||||
scale: Float,
|
scale: Float,
|
||||||
tintColor: Color?,
|
tintColor: Color?,
|
||||||
) {
|
) {
|
||||||
@ -331,21 +344,22 @@ private fun ClockLayer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val second = remember {
|
val second = remember {
|
||||||
Animatable(time.second.toFloat())
|
Animatable((time.second - defaultSecond).toFloat())
|
||||||
}
|
}
|
||||||
|
|
||||||
val minute = remember {
|
val minute = remember {
|
||||||
Animatable(time.minute.toFloat() + time.second.toFloat() / 60f)
|
Animatable((time.minute - defaultMinute).toFloat() + (time.second - defaultSecond).toFloat() / 60f)
|
||||||
}
|
}
|
||||||
|
|
||||||
val hour = remember {
|
val hour = remember {
|
||||||
Animatable(time.hour.toFloat() + time.minute.toFloat() / 60f)
|
Animatable((time.hour - defaultHour).toFloat() + (time.minute + defaultMinute).toFloat() / 60f)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(time) {
|
LaunchedEffect(time) {
|
||||||
val h = time.hour.toFloat() + time.minute.toFloat() / 60f
|
val h = (time.hour - defaultHour).toFloat() + (time.minute - defaultSecond).toFloat() / 60f
|
||||||
val m = time.minute.toFloat() + time.second.toFloat() / 60f
|
val m =
|
||||||
val s = time.second.toFloat() + (time.nano / 1000000f) / 1000f
|
(time.minute - defaultMinute).toFloat() + (time.second - defaultSecond).toFloat() / 60f
|
||||||
|
val s = (time.second - defaultSecond).toFloat() + (time.nano / 1000000f) / 1000f
|
||||||
second.snapTo(s)
|
second.snapTo(s)
|
||||||
hour.snapTo(h)
|
hour.snapTo(h)
|
||||||
minute.snapTo(m)
|
minute.snapTo(m)
|
||||||
|
|||||||
@ -15,6 +15,9 @@ data class ColorLayer(
|
|||||||
|
|
||||||
data class ClockLayer(
|
data class ClockLayer(
|
||||||
val sublayers: List<ClockSublayer>,
|
val sublayers: List<ClockSublayer>,
|
||||||
|
val defaultHour: Int = 0,
|
||||||
|
val defaultMinute: Int = 0,
|
||||||
|
val defaultSecond: Int = 0,
|
||||||
val scale: Float,
|
val scale: Float,
|
||||||
) : LauncherIconLayer
|
) : LauncherIconLayer
|
||||||
|
|
||||||
@ -38,6 +41,9 @@ data class TintedIconLayer(
|
|||||||
|
|
||||||
data class TintedClockLayer(
|
data class TintedClockLayer(
|
||||||
val sublayers: List<ClockSublayer>,
|
val sublayers: List<ClockSublayer>,
|
||||||
|
val defaultHour: Int = 0,
|
||||||
|
val defaultMinute: Int = 0,
|
||||||
|
val defaultSecond: Int = 0,
|
||||||
val scale: Float,
|
val scale: Float,
|
||||||
val color: Int = 0,
|
val color: Int = 0,
|
||||||
) : LauncherIconLayer
|
) : LauncherIconLayer
|
||||||
|
|||||||
@ -48,7 +48,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.appcompat)
|
||||||
api(libs.androidx.roomruntime)
|
api(libs.androidx.roomruntime)
|
||||||
kapt(libs.androidx.roomcompiler)
|
kapt(libs.androidx.roomcompiler)
|
||||||
implementation(libs.androidx.room)
|
api(libs.androidx.room)
|
||||||
implementation(libs.koin.android)
|
implementation(libs.koin.android)
|
||||||
|
|
||||||
implementation(project(":core:i18n"))
|
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.Room
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
import androidx.room.TypeConverters
|
import androidx.room.TypeConverters
|
||||||
|
import androidx.room.migration.Migration
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
import de.mm20.launcher2.database.entities.*
|
import de.mm20.launcher2.database.entities.*
|
||||||
import de.mm20.launcher2.database.migrations.Migration_10_11
|
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_17_18
|
||||||
import de.mm20.launcher2.database.migrations.Migration_18_19
|
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_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_6_7
|
||||||
import de.mm20.launcher2.database.migrations.Migration_7_8
|
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_8_9
|
||||||
@ -34,7 +36,7 @@ import de.mm20.launcher2.database.migrations.Migration_9_10
|
|||||||
WidgetEntity::class,
|
WidgetEntity::class,
|
||||||
CustomAttributeEntity::class,
|
CustomAttributeEntity::class,
|
||||||
SearchActionEntity::class,
|
SearchActionEntity::class,
|
||||||
], version = 20, exportSchema = true
|
], version = 21, exportSchema = true
|
||||||
)
|
)
|
||||||
@TypeConverters(ComponentNameConverter::class, StringListConverter::class)
|
@TypeConverters(ComponentNameConverter::class, StringListConverter::class)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
@ -101,6 +103,7 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
Migration_17_18(),
|
Migration_17_18(),
|
||||||
Migration_18_19(),
|
Migration_18_19(),
|
||||||
Migration_19_20(),
|
Migration_19_20(),
|
||||||
|
Migration_20_21(),
|
||||||
).build()
|
).build()
|
||||||
if (_instance == null) _instance = instance
|
if (_instance == null) _instance = instance
|
||||||
return instance
|
return instance
|
||||||
|
|||||||
@ -5,21 +5,20 @@ import androidx.room.*
|
|||||||
import de.mm20.launcher2.database.entities.IconEntity
|
import de.mm20.launcher2.database.entities.IconEntity
|
||||||
import de.mm20.launcher2.database.entities.IconPackEntity
|
import de.mm20.launcher2.database.entities.IconPackEntity
|
||||||
|
|
||||||
|
internal val AppTypes = listOf("app", "calendar", "clock")
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface IconDao {
|
interface IconDao {
|
||||||
@Insert
|
@Insert
|
||||||
fun insertAll(icons: List<IconEntity>)
|
suspend fun insertAll(icons: List<IconEntity>)
|
||||||
|
|
||||||
@Query("SELECT drawable FROM Icons WHERE componentName = :componentName AND iconPack = :iconPack")
|
@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 getIconName(componentName: String, iconPack: String): String?
|
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")
|
@Query("SELECT * FROM Icons WHERE packageName = :packageName AND (activityName = :activityName OR activityName IS NULL) AND type IN ('app', 'calendar', 'clock')")
|
||||||
suspend fun getIcon(componentName: String, iconPack: String): IconEntity?
|
suspend fun getIconsFromAllPacks(packageName: String, activityName: String): List<IconEntity>
|
||||||
|
|
||||||
@Query("SELECT * FROM Icons WHERE componentName = :componentName AND (type = 'app' OR type = 'calendar')")
|
@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 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")
|
|
||||||
suspend fun searchIconPackIcons(
|
suspend fun searchIconPackIcons(
|
||||||
componentQuery: String,
|
componentQuery: String,
|
||||||
nameQuery: String,
|
nameQuery: String,
|
||||||
@ -28,30 +27,10 @@ interface IconDao {
|
|||||||
limit: Int = 100
|
limit: Int = 100
|
||||||
): List<IconEntity>
|
): List<IconEntity>
|
||||||
|
|
||||||
@Query("SELECT * FROM Icons WHERE (type = 'greyscale_icon') AND componentName LIKE :query GROUP BY componentName ORDER BY drawable LIMIT :limit")
|
@Query("DELETE FROM Icons WHERE iconPack = :iconPack")
|
||||||
suspend fun searchGreyscaleIcons(query: String, limit: Int = 100): List<IconEntity>
|
|
||||||
|
|
||||||
@Query("DELETE FROM Icons WHERE iconPack = :iconPack AND type != 'greyscale_icon'")
|
|
||||||
fun deleteIcons(iconPack: String)
|
fun deleteIcons(iconPack: String)
|
||||||
|
|
||||||
@Query("DELETE FROM Icons WHERE iconPack = :iconPack AND type = 'greyscale_icon'")
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
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
|
|
||||||
fun installIconPack(iconPack: IconPackEntity)
|
fun installIconPack(iconPack: IconPackEntity)
|
||||||
|
|
||||||
@Query("SELECT * FROM IconPack")
|
@Query("SELECT * FROM IconPack")
|
||||||
@ -60,35 +39,9 @@ interface IconDao {
|
|||||||
@Query("SELECT * FROM IconPack WHERE packageName = :packageName LIMIT 1")
|
@Query("SELECT * FROM IconPack WHERE packageName = :packageName LIMIT 1")
|
||||||
suspend fun getIconPack(packageName: String): IconPackEntity?
|
suspend fun getIconPack(packageName: String): IconPackEntity?
|
||||||
|
|
||||||
@Query("SELECT * FROM IconPack")
|
|
||||||
fun getInstalledIconPacksLiveData(): LiveData<List<IconPackEntity>>
|
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
fun deleteIconPack(iconPack: IconPackEntity)
|
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'")
|
@Query("SELECT drawable FROM Icons WHERE iconPack = :pack AND type = 'iconback'")
|
||||||
suspend fun getIconBacks(pack: String): List<String>
|
suspend fun getIconBacks(pack: String): List<String>
|
||||||
|
|
||||||
@ -100,7 +53,4 @@ interface IconDao {
|
|||||||
|
|
||||||
@Query("SELECT scale FROM IconPack WHERE packageName = :pack")
|
@Query("SELECT scale FROM IconPack WHERE packageName = :pack")
|
||||||
suspend fun getScale(pack: String): Float?
|
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
|
package de.mm20.launcher2.database.entities
|
||||||
|
|
||||||
import android.content.ComponentName
|
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
@Entity(tableName = "Icons")
|
@Entity(tableName = "Icons")
|
||||||
data class IconEntity(
|
data class IconEntity(
|
||||||
val type: String,
|
val type: String,
|
||||||
val componentName: ComponentName?,
|
val packageName: String? = null,
|
||||||
|
val activityName: String? = null,
|
||||||
val drawable: String?,
|
val drawable: String?,
|
||||||
|
val extras: String? = null,
|
||||||
val iconPack: String,
|
val iconPack: String,
|
||||||
val name: String?,
|
val name: String? = null,
|
||||||
val themed: Boolean = false,
|
val themed: Boolean = false,
|
||||||
@PrimaryKey(autoGenerate = true) val id : Long? = null
|
@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
|
package de.mm20.launcher2.data.customattrs
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import de.mm20.launcher2.database.entities.CustomAttributeEntity
|
import de.mm20.launcher2.database.entities.CustomAttributeEntity
|
||||||
import de.mm20.launcher2.ktx.jsonObjectOf
|
import de.mm20.launcher2.ktx.jsonObjectOf
|
||||||
@ -76,10 +77,20 @@ sealed class CustomIcon : CustomAttribute {
|
|||||||
val type = payload.getString("type")
|
val type = payload.getString("type")
|
||||||
return when (type) {
|
return when (type) {
|
||||||
"custom_icon_pack_icon" -> {
|
"custom_icon_pack_icon" -> {
|
||||||
|
val legacyComponentName = payload.optString("icon").let { ComponentName.unflattenFromString(it) }
|
||||||
|
if (legacyComponentName != null) {
|
||||||
CustomIconPackIcon(
|
CustomIconPackIcon(
|
||||||
iconComponentName = payload.getString("icon"),
|
iconPackageName = legacyComponentName.packageName,
|
||||||
|
iconActivityName = legacyComponentName.className,
|
||||||
iconPackPackage = payload.getString("icon_pack")
|
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" -> {
|
"custom_themed_icon" -> {
|
||||||
CustomThemedIcon(
|
CustomThemedIcon(
|
||||||
@ -105,12 +116,14 @@ sealed class CustomIcon : CustomAttribute {
|
|||||||
|
|
||||||
data class CustomIconPackIcon(
|
data class CustomIconPackIcon(
|
||||||
val iconPackPackage: String,
|
val iconPackPackage: String,
|
||||||
val iconComponentName: String,
|
val iconPackageName: String,
|
||||||
|
val iconActivityName: String?,
|
||||||
) : CustomIcon() {
|
) : CustomIcon() {
|
||||||
override fun toDatabaseValue(): String {
|
override fun toDatabaseValue(): String {
|
||||||
return jsonObjectOf(
|
return jsonObjectOf(
|
||||||
"type" to "custom_icon_pack_icon",
|
"type" to "custom_icon_pack_icon",
|
||||||
"icon" to iconComponentName,
|
"package" to iconPackageName,
|
||||||
|
"activity" to iconActivityName,
|
||||||
"icon_pack" to iconPackPackage,
|
"icon_pack" to iconPackPackage,
|
||||||
).toString()
|
).toString()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package de.mm20.launcher2.icons
|
package de.mm20.launcher2.icons
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageInfo
|
||||||
import android.content.pm.ResolveInfo
|
import android.content.pm.ResolveInfo
|
||||||
import de.mm20.launcher2.database.entities.IconPackEntity
|
import de.mm20.launcher2.database.entities.IconPackEntity
|
||||||
|
|
||||||
@ -8,13 +9,13 @@ data class IconPack(
|
|||||||
val name: String,
|
val name: String,
|
||||||
val packageName: String,
|
val packageName: String,
|
||||||
val version: String,
|
val version: String,
|
||||||
var scale: Float = 1f,
|
val scale: Float = 1f,
|
||||||
val themed: Boolean = false,
|
val themed: Boolean = false,
|
||||||
) {
|
) {
|
||||||
constructor(entity: IconPackEntity) : this(
|
constructor(entity: IconPackEntity) : this(
|
||||||
name = entity.name,
|
name = entity.name,
|
||||||
packageName = entity.packageName,
|
packageName = entity.packageName,
|
||||||
version = entity.packageName,
|
version = entity.version,
|
||||||
scale = entity.scale,
|
scale = entity.scale,
|
||||||
themed = entity.themed,
|
themed = entity.themed,
|
||||||
)
|
)
|
||||||
@ -30,6 +31,17 @@ data class IconPack(
|
|||||||
themed = themed,
|
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 {
|
fun toDatabaseEntity(): IconPackEntity {
|
||||||
return IconPackEntity(
|
return IconPackEntity(
|
||||||
name = name,
|
name = name,
|
||||||
|
|||||||
@ -1,33 +1,188 @@
|
|||||||
package de.mm20.launcher2.icons
|
package de.mm20.launcher2.icons
|
||||||
|
|
||||||
import android.content.ComponentName
|
|
||||||
import de.mm20.launcher2.database.entities.IconEntity
|
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(
|
sealed interface IconPackComponent {
|
||||||
val type: String,
|
val iconPack: String
|
||||||
val componentName: ComponentName?,
|
fun toDatabaseEntity(): IconEntity
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
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(
|
return IconEntity(
|
||||||
type = type,
|
type = "iconback",
|
||||||
componentName = componentName,
|
|
||||||
drawable = drawable,
|
drawable = drawable,
|
||||||
iconPack = iconPack,
|
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,
|
name = name,
|
||||||
themed = themed,
|
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 android.util.Log
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
|
||||||
import de.mm20.launcher2.database.AppDatabase
|
import de.mm20.launcher2.database.AppDatabase
|
||||||
import de.mm20.launcher2.icons.loaders.GrayscaleMapInstaller
|
import de.mm20.launcher2.icons.loaders.AppFilterIconPackInstaller
|
||||||
import de.mm20.launcher2.icons.loaders.IconPackInstaller
|
import de.mm20.launcher2.icons.loaders.GrayscaleMapIconPackInstaller
|
||||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||||
import de.mm20.launcher2.ktx.obtainTypedArrayOrNull
|
import de.mm20.launcher2.ktx.obtainTypedArrayOrNull
|
||||||
import de.mm20.launcher2.ktx.randomElementOrNull
|
import de.mm20.launcher2.ktx.randomElementOrNull
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@ -50,16 +50,33 @@ class IconPackManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateIconPacks() {
|
private var updateIconPacksMutex = Mutex()
|
||||||
withContext(Dispatchers.IO) {
|
suspend fun updateIconPacks(): Boolean {
|
||||||
IconPackInstaller(context, appDatabase).installIcons()
|
var iconsHaveBeenUpdated = false
|
||||||
GrayscaleMapInstaller(context, appDatabase).installIcons()
|
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(
|
suspend fun getIcon(
|
||||||
iconPack: String,
|
iconPack: String,
|
||||||
componentName: ComponentName,
|
packageName: String,
|
||||||
|
activityName: String?,
|
||||||
): LauncherIcon? {
|
): LauncherIcon? {
|
||||||
val res = try {
|
val res = try {
|
||||||
context.packageManager.getResourcesForApplication(iconPack)
|
context.packageManager.getResourcesForApplication(iconPack)
|
||||||
@ -68,70 +85,18 @@ class IconPackManager(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val iconDao = appDatabase.iconDao()
|
val iconDao = appDatabase.iconDao()
|
||||||
val icon = iconDao.getIcon(componentName.flattenToString(), iconPack)
|
val icon = iconDao.getIcon(packageName, activityName, iconPack)?.let { IconPackAppIcon(it) }
|
||||||
?: return null
|
?: return null
|
||||||
|
|
||||||
val drawableName = icon.drawable ?: return null
|
if (icon is CalendarIcon) {
|
||||||
|
return getIconPackCalendarIcon(icon, res)
|
||||||
if (icon.type == "calendar") {
|
} else if (icon is AppIcon) {
|
||||||
return getIconPackCalendarIcon(context, iconPack, drawableName, icon.themed)
|
return getIconPackStaticIcon(icon, res)
|
||||||
|
} else if (icon is ClockIcon) {
|
||||||
|
return getIconPackClockIcon(icon, res)
|
||||||
}
|
}
|
||||||
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 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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun generateIcon(
|
suspend fun generateIcon(
|
||||||
context: Context,
|
context: Context,
|
||||||
@ -233,10 +198,10 @@ class IconPackManager(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getAllIconPackIcons(componentName: ComponentName): List<IconPackIcon> {
|
suspend fun getAllIconPackIcons(componentName: ComponentName): List<IconPackAppIcon> {
|
||||||
val iconDao = appDatabase.iconDao()
|
val iconDao = appDatabase.iconDao()
|
||||||
return iconDao.getIconsFromAllPacks(componentName.flattenToString())
|
return iconDao.getIconsFromAllPacks(componentName.packageName, componentName.shortClassName)
|
||||||
.map { IconPackIcon(it) }
|
.mapNotNull { IconPackAppIcon(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getIconBack(iconPack: String): String? {
|
private suspend fun getIconBack(iconPack: String): String? {
|
||||||
@ -262,25 +227,90 @@ class IconPackManager(
|
|||||||
return iconDao.getScale(iconPack) ?: 1f
|
return iconDao.getScale(iconPack) ?: 1f
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getIconPackCalendarIcon(
|
private fun getIconPackStaticIcon(
|
||||||
context: Context,
|
icon: AppIcon,
|
||||||
iconPack: String,
|
resources: Resources,
|
||||||
baseIconName: String,
|
|
||||||
themed: Boolean,
|
|
||||||
): LauncherIcon? {
|
): LauncherIcon? {
|
||||||
val resources = try {
|
val resId =
|
||||||
context.packageManager.getResourcesForApplication(iconPack)
|
resources.getIdentifier(icon.drawable, "drawable", icon.iconPack).takeIf { it != 0 }
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
?: return null
|
||||||
|
val drawable = try {
|
||||||
|
ResourcesCompat.getDrawable(resources, resId, context.theme) ?: return null
|
||||||
|
} catch (e: Resources.NotFoundException) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val drawableIds = (1..31).map {
|
return when {
|
||||||
val drawableName = baseIconName + it
|
icon.themed && drawable is AdaptiveIconDrawable -> {
|
||||||
val id = resources.getIdentifier(drawableName, "drawable", iconPack)
|
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
|
if (id == 0) return null
|
||||||
id
|
id
|
||||||
}.toIntArray()
|
}.toIntArray()
|
||||||
|
|
||||||
if (themed) {
|
|
||||||
|
if (icon.themed) {
|
||||||
return ThemedDynamicCalendarIcon(
|
return ThemedDynamicCalendarIcon(
|
||||||
resources = resources,
|
resources = resources,
|
||||||
resourceIds = drawableIds,
|
resourceIds = drawableIds,
|
||||||
@ -292,142 +322,91 @@ class IconPackManager(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getThemedIcon(packageName: String): LauncherIcon? {
|
private fun getIconPackClockIcon(
|
||||||
val icon = getGreyscaleIcon(packageName) ?: return null
|
icon: ClockIcon,
|
||||||
val resId = icon.drawable?.toIntOrNull() ?: return null
|
resources: Resources,
|
||||||
try {
|
): LauncherIcon? {
|
||||||
val resources = context.packageManager.getResourcesForApplication(icon.iconPack)
|
var drawable = try {
|
||||||
return getThemedClockIcon(resources, resId) ?: getThemedCalendarIcon(
|
resources.getIdentifier(icon.drawable, "drawable", icon.iconPack).takeIf { it != 0 }
|
||||||
resources,
|
?.let { ResourcesCompat.getDrawable(resources, it, null) }
|
||||||
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()
|
|
||||||
)
|
|
||||||
} catch (e: Resources.NotFoundException) {
|
} catch (e: Resources.NotFoundException) {
|
||||||
return null
|
null
|
||||||
}
|
} ?: return null
|
||||||
}
|
|
||||||
|
|
||||||
private fun getThemedClockIcon(resources: Resources, resId: Int): LauncherIcon? {
|
val background = (drawable as? AdaptiveIconDrawable)?.background
|
||||||
try {
|
val foreground = (drawable as? AdaptiveIconDrawable)?.foreground ?: drawable
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
"com.android.launcher3.HOUR_LAYER_INDEX" -> {
|
if (foreground !is LayerDrawable) return null
|
||||||
i++
|
|
||||||
hourIndex = array.getInt(i, -1).takeIf { it != -1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
"com.android.launcher3.MINUTE_LAYER_INDEX" -> {
|
val layers = (0 until foreground.numberOfLayers).map {
|
||||||
i++
|
val drw = foreground.getDrawable(it)
|
||||||
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 && minuteIndex != null && hourIndex != null) {
|
|
||||||
|
|
||||||
return 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(
|
ClockSublayer(
|
||||||
drawable = drw,
|
drawable = drw,
|
||||||
role = when (it) {
|
role = when (it) {
|
||||||
hourIndex -> ClockSublayerRole.Hour
|
icon.config.hourLayer -> ClockSublayerRole.Hour
|
||||||
minuteIndex -> ClockSublayerRole.Minute
|
icon.config.minuteLayer -> ClockSublayerRole.Minute
|
||||||
|
icon.config.secondLayer -> ClockSublayerRole.Second
|
||||||
else -> ClockSublayerRole.Static
|
else -> ClockSublayerRole.Static
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
|
|
||||||
|
return when {
|
||||||
|
icon.themed && drawable is AdaptiveIconDrawable -> {
|
||||||
|
StaticLauncherIcon(
|
||||||
|
foregroundLayer = TintedClockLayer(
|
||||||
|
defaultHour = icon.config.defaultHour,
|
||||||
|
defaultMinute = icon.config.defaultMinute,
|
||||||
|
defaultSecond = icon.config.defaultSecond,
|
||||||
|
sublayers = layers,
|
||||||
scale = 1.5f,
|
scale = 1.5f,
|
||||||
),
|
),
|
||||||
backgroundLayer = ColorLayer()
|
backgroundLayer = ColorLayer(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Resources.NotFoundException) {
|
icon.themed -> {
|
||||||
}
|
StaticLauncherIcon(
|
||||||
return null
|
foregroundLayer = TintedClockLayer(
|
||||||
}
|
defaultHour = icon.config.defaultHour,
|
||||||
|
defaultMinute = icon.config.defaultMinute,
|
||||||
private fun getThemedCalendarIcon(
|
defaultSecond = icon.config.defaultSecond,
|
||||||
resources: Resources,
|
sublayers = layers,
|
||||||
resId: Int,
|
scale = 1f,
|
||||||
iconProviderPackage: String
|
),
|
||||||
): LauncherIcon? {
|
backgroundLayer = ColorLayer(),
|
||||||
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
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun searchIconPackIcon(query: String, iconPack: IconPack?): List<IconPackIcon> {
|
suspend fun searchIconPackIcon(query: String, iconPack: IconPack?): List<IconPackAppIcon> {
|
||||||
val iconDao = appDatabase.iconDao()
|
val iconDao = appDatabase.iconDao()
|
||||||
val drawableQuery = query.replace(" ", "_").lowercase()
|
val drawableQuery = query.replace(" ", "_").lowercase()
|
||||||
return iconDao.searchIconPackIcons(
|
return iconDao.searchIconPackIcons(
|
||||||
@ -435,17 +414,11 @@ class IconPackManager(
|
|||||||
componentQuery = "%$query%",
|
componentQuery = "%$query%",
|
||||||
nameQuery = "%$query%",
|
nameQuery = "%$query%",
|
||||||
iconPack = iconPack?.packageName,
|
iconPack = iconPack?.packageName,
|
||||||
).map {
|
).mapNotNull {
|
||||||
IconPackIcon(it)
|
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
|
package de.mm20.launcher2.icons
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
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.IconProvider
|
||||||
import de.mm20.launcher2.icons.providers.PlaceholderIconProvider
|
import de.mm20.launcher2.icons.providers.PlaceholderIconProvider
|
||||||
import de.mm20.launcher2.icons.providers.SystemIconProvider
|
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.ThemedPlaceholderIconProvider
|
||||||
import de.mm20.launcher2.icons.providers.getFirstIcon
|
import de.mm20.launcher2.icons.providers.getFirstIcon
|
||||||
import de.mm20.launcher2.icons.transformations.ForceThemedIconTransformation
|
import de.mm20.launcher2.icons.transformations.ForceThemedIconTransformation
|
||||||
@ -39,11 +39,13 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.channelFlow
|
import kotlinx.coroutines.flow.channelFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.internal.ChannelFlow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@ -67,6 +69,11 @@ class IconRepository(
|
|||||||
private var iconProviders: MutableStateFlow<List<IconProvider>> = MutableStateFlow(listOf())
|
private var iconProviders: MutableStateFlow<List<IconProvider>> = MutableStateFlow(listOf())
|
||||||
private var placeholderProvider: IconProvider? = null
|
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>> =
|
private var transformations: MutableStateFlow<List<LauncherIconTransformation>> =
|
||||||
MutableStateFlow(
|
MutableStateFlow(
|
||||||
listOf()
|
listOf()
|
||||||
@ -83,8 +90,11 @@ class IconRepository(
|
|||||||
addDataScheme("package")
|
addDataScheme("package")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
iconPacksUpdated.tryEmit(Unit)
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
dataStore.data.map { it.icons }.distinctUntilChanged().collectLatest { settings ->
|
dataStore.data.map { it.icons }.distinctUntilChanged().collectLatest { settings ->
|
||||||
|
iconPacksUpdated.collectLatest {
|
||||||
val placeholderProvider = if (settings.themedIcons) {
|
val placeholderProvider = if (settings.themedIcons) {
|
||||||
ThemedPlaceholderIconProvider(context)
|
ThemedPlaceholderIconProvider(context)
|
||||||
} else {
|
} else {
|
||||||
@ -106,9 +116,6 @@ class IconRepository(
|
|||||||
Log.w("MM20", "Icon pack ${settings.iconPack} not found")
|
Log.w("MM20", "Icon pack ${settings.iconPack} not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (settings.themedIcons) {
|
|
||||||
providers.add(ThemedIconProvider(iconPackManager))
|
|
||||||
}
|
|
||||||
providers.add(DynamicClockIconProvider(context, settings.themedIcons))
|
providers.add(DynamicClockIconProvider(context, settings.themedIcons))
|
||||||
providers.add(CalendarIconProvider(context, settings.themedIcons))
|
providers.add(CalendarIconProvider(context, settings.themedIcons))
|
||||||
if (!isAtLeastApiLevel(33)) {
|
if (!isAtLeastApiLevel(33)) {
|
||||||
@ -131,6 +138,7 @@ class IconRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getIcon(searchable: SavableSearchable, size: Int): Flow<LauncherIcon> = channelFlow {
|
fun getIcon(searchable: SavableSearchable, size: Int): Flow<LauncherIcon> = channelFlow {
|
||||||
@ -215,7 +223,9 @@ class IconRepository(
|
|||||||
|
|
||||||
fun requestIconPackListUpdate() {
|
fun requestIconPackListUpdate() {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
iconPackManager.updateIconPacks()
|
iconPackManager.updateIconPacks().also {
|
||||||
|
if (it)iconPacksUpdated.tryEmit(Unit)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,24 +295,14 @@ class IconRepository(
|
|||||||
iconPackIcons.mapNotNull {
|
iconPackIcons.mapNotNull {
|
||||||
CustomIconPackIcon(
|
CustomIconPackIcon(
|
||||||
iconPackPackage = it.iconPack,
|
iconPackPackage = it.iconPack,
|
||||||
iconComponentName = it.componentName?.flattenToString()
|
iconActivityName = it.activityName,
|
||||||
?: return@mapNotNull null
|
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(
|
transformationOptions.add(
|
||||||
ForceThemedIcon
|
ForceThemedIcon
|
||||||
)
|
)
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
transformationOptions.add(
|
transformationOptions.add(
|
||||||
ForceThemedIcon
|
ForceThemedIcon
|
||||||
@ -359,31 +359,18 @@ class IconRepository(
|
|||||||
suspend fun searchCustomIcons(query: String, iconPack: IconPack?): List<CustomIconWithPreview> {
|
suspend fun searchCustomIcons(query: String, iconPack: IconPack?): List<CustomIconWithPreview> {
|
||||||
val transformations = this.transformations.first()
|
val transformations = this.transformations.first()
|
||||||
val iconPackIcons = iconPackManager.searchIconPackIcon(query, iconPack).mapNotNull {
|
val iconPackIcons = iconPackManager.searchIconPackIcon(query, iconPack).mapNotNull {
|
||||||
val componentName = it.componentName ?: return@mapNotNull null
|
|
||||||
|
|
||||||
CustomIconWithPreview(
|
CustomIconWithPreview(
|
||||||
customIcon = CustomIconPackIcon(
|
customIcon = CustomIconPackIcon(
|
||||||
iconPackPackage = it.iconPack,
|
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
|
?.transform(transformations) ?: return@mapNotNull null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val themedIcons = iconPackManager.searchThemedIcons(query).mapNotNull {
|
return iconPackIcons
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCustomIcon(searchable: SavableSearchable, icon: CustomIcon?) {
|
fun setCustomIcon(searchable: SavableSearchable, icon: CustomIcon?) {
|
||||||
|
|||||||
@ -145,23 +145,6 @@ fun AdaptiveIconDrawableCompat.toLauncherIcon(
|
|||||||
if (clock != null && clockForeground != null) {
|
if (clock != null && clockForeground != null) {
|
||||||
val clockLayers = (0 until clockForeground.numberOfLayers).map {
|
val clockLayers = (0 until clockForeground.numberOfLayers).map {
|
||||||
val drw = clockForeground.getDrawable(it)
|
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(
|
ClockSublayer(
|
||||||
drawable = drw,
|
drawable = drw,
|
||||||
role = when (it) {
|
role = when (it) {
|
||||||
@ -175,6 +158,9 @@ fun AdaptiveIconDrawableCompat.toLauncherIcon(
|
|||||||
if (themed) {
|
if (themed) {
|
||||||
return StaticLauncherIcon(
|
return StaticLauncherIcon(
|
||||||
foregroundLayer = TintedClockLayer(
|
foregroundLayer = TintedClockLayer(
|
||||||
|
defaultHour = clock.defaultHour,
|
||||||
|
defaultMinute = clock.defaultMinute,
|
||||||
|
defaultSecond = clock.defaultSecond,
|
||||||
sublayers = clockLayers,
|
sublayers = clockLayers,
|
||||||
scale = 1.5f,
|
scale = 1.5f,
|
||||||
),
|
),
|
||||||
@ -183,6 +169,9 @@ fun AdaptiveIconDrawableCompat.toLauncherIcon(
|
|||||||
}
|
}
|
||||||
return StaticLauncherIcon(
|
return StaticLauncherIcon(
|
||||||
foregroundLayer = ClockLayer(
|
foregroundLayer = ClockLayer(
|
||||||
|
defaultHour = clock.defaultHour,
|
||||||
|
defaultMinute = clock.defaultMinute,
|
||||||
|
defaultSecond = clock.defaultSecond,
|
||||||
sublayers = clockLayers,
|
sublayers = clockLayers,
|
||||||
scale = 1.5f,
|
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
|
package de.mm20.launcher2.icons.loaders
|
||||||
|
|
||||||
import android.content.ComponentName
|
import androidx.room.withTransaction
|
||||||
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.database.AppDatabase
|
||||||
|
import de.mm20.launcher2.icons.BuildConfig
|
||||||
import de.mm20.launcher2.icons.IconPack
|
import de.mm20.launcher2.icons.IconPack
|
||||||
import de.mm20.launcher2.icons.IconPackIcon
|
import de.mm20.launcher2.icons.IconPackComponent
|
||||||
import org.xmlpull.v1.XmlPullParser
|
|
||||||
import org.xmlpull.v1.XmlPullParserException
|
|
||||||
import org.xmlpull.v1.XmlPullParserFactory
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.Reader
|
|
||||||
|
|
||||||
class IconPackInstaller(
|
abstract class IconPackInstaller(
|
||||||
private val context: Context,
|
|
||||||
private val database: AppDatabase,
|
private val database: AppDatabase,
|
||||||
) {
|
) {
|
||||||
|
suspend fun install(iconPack: IconPack) {
|
||||||
fun installIcons() {
|
var pack = iconPack
|
||||||
val packs = loadInstalledPacks(context)
|
val dao = database.iconDao()
|
||||||
val iconDao = database.iconDao()
|
database.withTransaction {
|
||||||
|
dao.deleteIconPack(iconPack.toDatabaseEntity())
|
||||||
for (pack in packs) {
|
dao.deleteIcons(iconPack.packageName)
|
||||||
try {
|
val icons = mutableListOf<IconPackComponent>()
|
||||||
installIconPack(pack)
|
val installerScope = object: IconPackInstallerScope {
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
override suspend fun addIcon(icon: IconPackComponent) {
|
||||||
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)
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (icons.size >= 100) {
|
if (icons.size >= 100) {
|
||||||
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
|
dao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||||
icons.clear()
|
icons.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (icons.isNotEmpty()) {
|
override suspend fun updatePackInfo(update: (IconPack) -> IconPack) {
|
||||||
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
|
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? {
|
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
|
||||||
return iconPackManager.getIcon(
|
return iconPackManager.getIcon(
|
||||||
customIcon.iconPackPackage,
|
customIcon.iconPackPackage,
|
||||||
ComponentName.unflattenFromString(customIcon.iconComponentName) ?: return null
|
customIcon.iconPackageName,
|
||||||
|
customIcon.iconActivityName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -10,6 +10,6 @@ class CustomThemedIconProvider(
|
|||||||
private val iconPackManager: IconPackManager,
|
private val iconPackManager: IconPackManager,
|
||||||
): IconProvider {
|
): IconProvider {
|
||||||
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
|
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? {
|
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
|
||||||
if (searchable !is LauncherApp) return null
|
if (searchable !is LauncherApp) return null
|
||||||
|
|
||||||
val component = ComponentName(searchable.`package`, searchable.activity)
|
return iconPackManager.getIcon(iconPack.packageName, searchable.`package`, searchable.activity)
|
||||||
return iconPackManager.getIcon(iconPack.packageName, component)
|
|
||||||
?: iconPackManager.generateIcon(
|
?: iconPackManager.generateIcon(
|
||||||
context,
|
context,
|
||||||
iconPack.packageName,
|
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(
|
is ClockLayer -> TintedClockLayer(
|
||||||
scale = layer.scale,
|
scale = layer.scale,
|
||||||
color = 0,
|
color = 0,
|
||||||
|
defaultHour = layer.defaultHour,
|
||||||
|
defaultMinute = layer.defaultMinute,
|
||||||
|
defaultSecond = layer.defaultSecond,
|
||||||
sublayers = layer.sublayers,
|
sublayers = layer.sublayers,
|
||||||
)
|
)
|
||||||
is ColorLayer -> layer.copy(color = 0)
|
is ColorLayer -> layer.copy(color = 0)
|
||||||
|
|||||||
@ -14,6 +14,9 @@ internal class ForceThemedIconTransformation : LauncherIconTransformation {
|
|||||||
return when(layer) {
|
return when(layer) {
|
||||||
is ClockLayer -> TintedClockLayer(
|
is ClockLayer -> TintedClockLayer(
|
||||||
scale = layer.scale,
|
scale = layer.scale,
|
||||||
|
defaultHour = layer.defaultHour,
|
||||||
|
defaultMinute = layer.defaultMinute,
|
||||||
|
defaultSecond = layer.defaultSecond,
|
||||||
sublayers = layer.sublayers,
|
sublayers = layer.sublayers,
|
||||||
)
|
)
|
||||||
is ColorLayer -> layer.copy(color = 0)
|
is ColorLayer -> layer.copy(color = 0)
|
||||||
|
|||||||
@ -138,7 +138,7 @@ dependencyResolutionManagement {
|
|||||||
library("androidx.datastore", "androidx.datastore", "datastore")
|
library("androidx.datastore", "androidx.datastore", "datastore")
|
||||||
.version("1.0.0")
|
.version("1.0.0")
|
||||||
|
|
||||||
version("androidx.room", "2.5.0-alpha03")
|
version("androidx.room", "2.5.0")
|
||||||
library("androidx.roomruntime", "androidx.room", "room-runtime")
|
library("androidx.roomruntime", "androidx.room", "room-runtime")
|
||||||
.versionRef("androidx.room")
|
.versionRef("androidx.room")
|
||||||
library("androidx.roomcompiler", "androidx.room", "room-compiler")
|
library("androidx.roomcompiler", "androidx.room", "room-compiler")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user