Restructure / cleanup / improve icon pack handling
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package de.mm20.launcher2.icons
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.ResolveInfo
|
||||
import de.mm20.launcher2.database.entities.IconPackEntity
|
||||
|
||||
@@ -8,13 +9,13 @@ data class IconPack(
|
||||
val name: String,
|
||||
val packageName: String,
|
||||
val version: String,
|
||||
var scale: Float = 1f,
|
||||
val scale: Float = 1f,
|
||||
val themed: Boolean = false,
|
||||
) {
|
||||
constructor(entity: IconPackEntity) : this(
|
||||
name = entity.name,
|
||||
packageName = entity.packageName,
|
||||
version = entity.packageName,
|
||||
version = entity.version,
|
||||
scale = entity.scale,
|
||||
themed = entity.themed,
|
||||
)
|
||||
@@ -30,6 +31,17 @@ data class IconPack(
|
||||
themed = themed,
|
||||
)
|
||||
|
||||
internal constructor(
|
||||
context: Context,
|
||||
packageInfo: PackageInfo,
|
||||
themed: Boolean = false
|
||||
): this(
|
||||
name = packageInfo.applicationInfo.loadLabel(context.packageManager).toString(),
|
||||
packageName = packageInfo.packageName,
|
||||
version = context.packageManager.getPackageInfo(packageInfo.packageName, 0).versionName,
|
||||
themed = themed,
|
||||
)
|
||||
|
||||
fun toDatabaseEntity(): IconPackEntity {
|
||||
return IconPackEntity(
|
||||
name = name,
|
||||
|
||||
@@ -1,33 +1,188 @@
|
||||
package de.mm20.launcher2.icons
|
||||
|
||||
import android.content.ComponentName
|
||||
import de.mm20.launcher2.database.entities.IconEntity
|
||||
import de.mm20.launcher2.icons.compat.ClockIconConfig
|
||||
import de.mm20.launcher2.ktx.jsonObjectOf
|
||||
import org.json.JSONObject
|
||||
|
||||
data class IconPackIcon(
|
||||
val type: String,
|
||||
val componentName: ComponentName?,
|
||||
val drawable: String?,
|
||||
val iconPack: String,
|
||||
val themed: Boolean = false,
|
||||
val name: String? = null,
|
||||
) {
|
||||
constructor(entity: IconEntity) : this(
|
||||
type = entity.type,
|
||||
componentName = entity.componentName,
|
||||
drawable = entity.drawable,
|
||||
iconPack = entity.iconPack,
|
||||
name = entity.name,
|
||||
themed = entity.themed,
|
||||
)
|
||||
sealed interface IconPackComponent {
|
||||
val iconPack: String
|
||||
fun toDatabaseEntity(): IconEntity
|
||||
}
|
||||
|
||||
fun toDatabaseEntity(): IconEntity {
|
||||
sealed interface IconPackAppIcon: IconPackComponent {
|
||||
val packageName: String
|
||||
val activityName: String?
|
||||
val name: String?
|
||||
val themed: Boolean
|
||||
}
|
||||
|
||||
data class IconBack(
|
||||
val drawable: String,
|
||||
override val iconPack: String,
|
||||
): IconPackComponent {
|
||||
override fun toDatabaseEntity(): IconEntity {
|
||||
return IconEntity(
|
||||
type = type,
|
||||
componentName = componentName,
|
||||
type = "iconback",
|
||||
drawable = drawable,
|
||||
iconPack = iconPack,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class IconUpon(
|
||||
val drawable: String,
|
||||
override val iconPack: String,
|
||||
): IconPackComponent {
|
||||
override fun toDatabaseEntity(): IconEntity {
|
||||
return IconEntity(
|
||||
type = "iconupon",
|
||||
drawable = drawable,
|
||||
iconPack = iconPack,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class IconMask(
|
||||
val drawable: String,
|
||||
override val iconPack: String,
|
||||
): IconPackComponent {
|
||||
override fun toDatabaseEntity(): IconEntity {
|
||||
return IconEntity(
|
||||
type = "iconmask",
|
||||
drawable = drawable,
|
||||
iconPack = iconPack,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class AppIcon(
|
||||
val drawable: String,
|
||||
override val iconPack: String,
|
||||
override val packageName: String,
|
||||
override val activityName: String?,
|
||||
override val name: String?,
|
||||
override val themed: Boolean = false,
|
||||
): IconPackComponent, IconPackAppIcon {
|
||||
override fun toDatabaseEntity(): IconEntity {
|
||||
return IconEntity(
|
||||
type = "app",
|
||||
packageName = packageName,
|
||||
activityName = activityName,
|
||||
drawable = drawable,
|
||||
name = name,
|
||||
iconPack = iconPack,
|
||||
themed = themed,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class CalendarIcon(
|
||||
val drawables: List<String>,
|
||||
override val iconPack: String,
|
||||
override val packageName: String,
|
||||
override val activityName: String? = null,
|
||||
override val name: String? = null,
|
||||
override val themed: Boolean = false,
|
||||
): IconPackComponent, IconPackAppIcon {
|
||||
override fun toDatabaseEntity(): IconEntity {
|
||||
return IconEntity(
|
||||
type = "calendar",
|
||||
drawable = drawables.joinToString(","),
|
||||
iconPack = iconPack,
|
||||
packageName = packageName,
|
||||
activityName = activityName,
|
||||
name = name,
|
||||
themed = themed,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class ClockIcon(
|
||||
val drawable: String,
|
||||
override val iconPack: String,
|
||||
override val packageName: String,
|
||||
override val activityName: String? = null,
|
||||
override val name: String? = null,
|
||||
override val themed: Boolean,
|
||||
val config: ClockIconConfig,
|
||||
): IconPackComponent, IconPackAppIcon {
|
||||
override fun toDatabaseEntity(): IconEntity {
|
||||
return IconEntity(
|
||||
type = "clock",
|
||||
packageName = packageName,
|
||||
activityName = activityName,
|
||||
drawable = drawable,
|
||||
name = name,
|
||||
iconPack = iconPack,
|
||||
themed = themed,
|
||||
extras = jsonObjectOf(
|
||||
"defaultSecond" to config.defaultSecond,
|
||||
"defaultMinute" to config.defaultMinute,
|
||||
"defaultHour" to config.defaultHour,
|
||||
"hourLayer" to config.hourLayer,
|
||||
"minuteLayer" to config.minuteLayer,
|
||||
"secondLayer" to config.secondLayer,
|
||||
).toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun Icon(entity: IconEntity): IconPackComponent? {
|
||||
return when(entity.type) {
|
||||
"iconback" -> IconBack(
|
||||
drawable = entity.drawable ?: return null,
|
||||
iconPack = entity.iconPack,
|
||||
)
|
||||
"iconupon" -> IconUpon(
|
||||
drawable = entity.drawable ?: return null,
|
||||
iconPack = entity.iconPack,
|
||||
)
|
||||
"iconmask" -> IconMask(
|
||||
drawable = entity.drawable ?: return null,
|
||||
iconPack = entity.iconPack,
|
||||
)
|
||||
"app" -> AppIcon(
|
||||
drawable = entity.drawable ?: return null,
|
||||
iconPack = entity.iconPack,
|
||||
packageName = entity.packageName ?: return null,
|
||||
activityName = entity.activityName,
|
||||
themed = entity.themed,
|
||||
name = entity.name,
|
||||
)
|
||||
"calendar" -> CalendarIcon(
|
||||
drawables = entity.drawable?.split(",") ?: return null,
|
||||
iconPack = entity.iconPack,
|
||||
themed = entity.themed,
|
||||
packageName = entity.packageName ?: return null,
|
||||
activityName = entity.activityName,
|
||||
name = entity.name,
|
||||
)
|
||||
"clock" -> {
|
||||
val config = JSONObject(entity.extras ?: return null)
|
||||
ClockIcon(
|
||||
drawable = entity.drawable!!,
|
||||
iconPack = entity.iconPack,
|
||||
packageName = entity.packageName ?: return null,
|
||||
name = entity.name,
|
||||
activityName = entity.activityName,
|
||||
themed = entity.themed,
|
||||
config = ClockIconConfig(
|
||||
defaultSecond = config.optInt("defaultSecond", 0),
|
||||
defaultMinute = config.optInt("defaultMinute", 0),
|
||||
defaultHour = config.optInt("defaultHour", 0),
|
||||
hourLayer = config.optInt("hourLayer", 0),
|
||||
minuteLayer = config.optInt("minuteLayer", 0),
|
||||
secondLayer = config.optInt("secondLayer", 0),
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun IconPackAppIcon(entity: IconEntity): IconPackAppIcon? {
|
||||
if (entity.type != "app" && entity.type != "calendar" && entity.type != "clock") return null
|
||||
return Icon(entity) as? IconPackAppIcon
|
||||
}
|
||||
|
||||
@@ -18,14 +18,14 @@ import android.graphics.drawable.RotateDrawable
|
||||
import android.util.Log
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.database.AppDatabase
|
||||
import de.mm20.launcher2.icons.loaders.GrayscaleMapInstaller
|
||||
import de.mm20.launcher2.icons.loaders.IconPackInstaller
|
||||
import de.mm20.launcher2.icons.loaders.AppFilterIconPackInstaller
|
||||
import de.mm20.launcher2.icons.loaders.GrayscaleMapIconPackInstaller
|
||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||
import de.mm20.launcher2.ktx.obtainTypedArrayOrNull
|
||||
import de.mm20.launcher2.ktx.randomElementOrNull
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@@ -50,16 +50,33 @@ class IconPackManager(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateIconPacks() {
|
||||
withContext(Dispatchers.IO) {
|
||||
IconPackInstaller(context, appDatabase).installIcons()
|
||||
GrayscaleMapInstaller(context, appDatabase).installIcons()
|
||||
private var updateIconPacksMutex = Mutex()
|
||||
suspend fun updateIconPacks(): Boolean {
|
||||
var iconsHaveBeenUpdated = false
|
||||
updateIconPacksMutex.lock()
|
||||
val installers = listOf(
|
||||
AppFilterIconPackInstaller(context, appDatabase),
|
||||
GrayscaleMapIconPackInstaller(context, appDatabase),
|
||||
)
|
||||
for (installer in installers) {
|
||||
val iconPacks = installer.getInstalledIconPacks()
|
||||
for (pack in iconPacks) {
|
||||
if (!installer.isInstalledAndUpToDate(pack)) {
|
||||
installer.install(pack)
|
||||
iconsHaveBeenUpdated = true
|
||||
} else {
|
||||
Log.d("MM20", "Icon pack ${pack.packageName} is up to date")
|
||||
}
|
||||
}
|
||||
}
|
||||
updateIconPacksMutex.unlock()
|
||||
return iconsHaveBeenUpdated
|
||||
}
|
||||
|
||||
suspend fun getIcon(
|
||||
iconPack: String,
|
||||
componentName: ComponentName,
|
||||
packageName: String,
|
||||
activityName: String?,
|
||||
): LauncherIcon? {
|
||||
val res = try {
|
||||
context.packageManager.getResourcesForApplication(iconPack)
|
||||
@@ -68,69 +85,17 @@ class IconPackManager(
|
||||
return null
|
||||
}
|
||||
val iconDao = appDatabase.iconDao()
|
||||
val icon = iconDao.getIcon(componentName.flattenToString(), iconPack)
|
||||
val icon = iconDao.getIcon(packageName, activityName, iconPack)?.let { IconPackAppIcon(it) }
|
||||
?: return null
|
||||
|
||||
val drawableName = icon.drawable ?: return null
|
||||
|
||||
if (icon.type == "calendar") {
|
||||
return getIconPackCalendarIcon(context, iconPack, drawableName, icon.themed)
|
||||
}
|
||||
val resId = res.getIdentifier(drawableName, "drawable", iconPack).takeIf { it != 0 }
|
||||
?: return null
|
||||
val drawable = try {
|
||||
ResourcesCompat.getDrawable(res, resId, context.theme) ?: return null
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
return null
|
||||
}
|
||||
return when {
|
||||
icon.themed && drawable is AdaptiveIconDrawable -> {
|
||||
if (isAtLeastApiLevel(33) && drawable.monochrome != null) {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = StaticIconLayer(
|
||||
icon = drawable.monochrome!!,
|
||||
scale = 1f,
|
||||
),
|
||||
backgroundLayer = ColorLayer(),
|
||||
)
|
||||
} else {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = TintedIconLayer(
|
||||
icon = drawable.foreground,
|
||||
scale = 1.5f,
|
||||
),
|
||||
backgroundLayer = ColorLayer(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
drawable is AdaptiveIconDrawable -> {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = drawable.foreground?.let {
|
||||
StaticIconLayer(
|
||||
icon = it,
|
||||
scale = 1.5f,
|
||||
)
|
||||
} ?: TransparentLayer,
|
||||
backgroundLayer = drawable.background?.let {
|
||||
StaticIconLayer(
|
||||
icon = it,
|
||||
scale = 1.5f,
|
||||
)
|
||||
} ?: TransparentLayer,
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
StaticLauncherIcon(
|
||||
foregroundLayer = StaticIconLayer(
|
||||
icon = drawable,
|
||||
scale = 1f
|
||||
),
|
||||
backgroundLayer = TransparentLayer
|
||||
)
|
||||
}
|
||||
if (icon is CalendarIcon) {
|
||||
return getIconPackCalendarIcon(icon, res)
|
||||
} else if (icon is AppIcon) {
|
||||
return getIconPackStaticIcon(icon, res)
|
||||
} else if (icon is ClockIcon) {
|
||||
return getIconPackClockIcon(icon, res)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun generateIcon(
|
||||
@@ -233,10 +198,10 @@ class IconPackManager(
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getAllIconPackIcons(componentName: ComponentName): List<IconPackIcon> {
|
||||
suspend fun getAllIconPackIcons(componentName: ComponentName): List<IconPackAppIcon> {
|
||||
val iconDao = appDatabase.iconDao()
|
||||
return iconDao.getIconsFromAllPacks(componentName.flattenToString())
|
||||
.map { IconPackIcon(it) }
|
||||
return iconDao.getIconsFromAllPacks(componentName.packageName, componentName.shortClassName)
|
||||
.mapNotNull { IconPackAppIcon(it) }
|
||||
}
|
||||
|
||||
private suspend fun getIconBack(iconPack: String): String? {
|
||||
@@ -262,25 +227,90 @@ class IconPackManager(
|
||||
return iconDao.getScale(iconPack) ?: 1f
|
||||
}
|
||||
|
||||
private fun getIconPackCalendarIcon(
|
||||
context: Context,
|
||||
iconPack: String,
|
||||
baseIconName: String,
|
||||
themed: Boolean,
|
||||
private fun getIconPackStaticIcon(
|
||||
icon: AppIcon,
|
||||
resources: Resources,
|
||||
): LauncherIcon? {
|
||||
val resources = try {
|
||||
context.packageManager.getResourcesForApplication(iconPack)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
val resId =
|
||||
resources.getIdentifier(icon.drawable, "drawable", icon.iconPack).takeIf { it != 0 }
|
||||
?: return null
|
||||
val drawable = try {
|
||||
ResourcesCompat.getDrawable(resources, resId, context.theme) ?: return null
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
return null
|
||||
}
|
||||
val drawableIds = (1..31).map {
|
||||
val drawableName = baseIconName + it
|
||||
val id = resources.getIdentifier(drawableName, "drawable", iconPack)
|
||||
return when {
|
||||
icon.themed && drawable is AdaptiveIconDrawable -> {
|
||||
if (isAtLeastApiLevel(33) && drawable.monochrome != null) {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = TintedIconLayer(
|
||||
icon = drawable.monochrome!!,
|
||||
scale = 1f,
|
||||
),
|
||||
backgroundLayer = ColorLayer(),
|
||||
)
|
||||
} else {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = TintedIconLayer(
|
||||
icon = drawable.foreground,
|
||||
scale = 1.5f,
|
||||
),
|
||||
backgroundLayer = ColorLayer(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
icon.themed -> {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = TintedIconLayer(
|
||||
icon = drawable,
|
||||
scale = 0.5f,
|
||||
),
|
||||
backgroundLayer = ColorLayer(),
|
||||
)
|
||||
}
|
||||
|
||||
drawable is AdaptiveIconDrawable -> {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = drawable.foreground?.let {
|
||||
StaticIconLayer(
|
||||
icon = it,
|
||||
scale = 1.5f,
|
||||
)
|
||||
} ?: TransparentLayer,
|
||||
backgroundLayer = drawable.background?.let {
|
||||
StaticIconLayer(
|
||||
icon = it,
|
||||
scale = 1.5f,
|
||||
)
|
||||
} ?: TransparentLayer,
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
StaticLauncherIcon(
|
||||
foregroundLayer = StaticIconLayer(
|
||||
icon = drawable,
|
||||
scale = 1f
|
||||
),
|
||||
backgroundLayer = TransparentLayer
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIconPackCalendarIcon(
|
||||
icon: CalendarIcon,
|
||||
resources: Resources,
|
||||
): LauncherIcon? {
|
||||
val drawableIds = icon.drawables.map {
|
||||
val id = resources.getIdentifier(it, "drawable", icon.iconPack)
|
||||
if (id == 0) return null
|
||||
id
|
||||
}.toIntArray()
|
||||
|
||||
if (themed) {
|
||||
|
||||
if (icon.themed) {
|
||||
return ThemedDynamicCalendarIcon(
|
||||
resources = resources,
|
||||
resourceIds = drawableIds,
|
||||
@@ -292,142 +322,91 @@ class IconPackManager(
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getThemedIcon(packageName: String): LauncherIcon? {
|
||||
val icon = getGreyscaleIcon(packageName) ?: return null
|
||||
val resId = icon.drawable?.toIntOrNull() ?: return null
|
||||
try {
|
||||
val resources = context.packageManager.getResourcesForApplication(icon.iconPack)
|
||||
return getThemedClockIcon(resources, resId) ?: getThemedCalendarIcon(
|
||||
resources,
|
||||
resId,
|
||||
iconProviderPackage = icon.iconPack
|
||||
) ?: getThemedStaticIcon(resources, resId)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
suspend fun getGreyscaleIcon(packageName: String): IconPackIcon? {
|
||||
val iconDao = AppDatabase.getInstance(context).iconDao()
|
||||
return iconDao.getGreyscaleIcon(ComponentName(packageName, packageName).flattenToString())
|
||||
?.let { IconPackIcon(it) }
|
||||
|
||||
}
|
||||
|
||||
private fun getThemedStaticIcon(resources: Resources, resId: Int): LauncherIcon? {
|
||||
try {
|
||||
val fg = ResourcesCompat.getDrawable(resources, resId, null) ?: return null
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = TintedIconLayer(
|
||||
icon = fg,
|
||||
scale = 0.5f,
|
||||
),
|
||||
backgroundLayer = ColorLayer()
|
||||
)
|
||||
private fun getIconPackClockIcon(
|
||||
icon: ClockIcon,
|
||||
resources: Resources,
|
||||
): LauncherIcon? {
|
||||
var drawable = try {
|
||||
resources.getIdentifier(icon.drawable, "drawable", icon.iconPack).takeIf { it != 0 }
|
||||
?.let { ResourcesCompat.getDrawable(resources, it, null) }
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
null
|
||||
} ?: return null
|
||||
|
||||
private fun getThemedClockIcon(resources: Resources, resId: Int): LauncherIcon? {
|
||||
try {
|
||||
val array = resources.obtainTypedArrayOrNull(resId) ?: return null
|
||||
var i = 0
|
||||
var drawable: LayerDrawable? = null
|
||||
var minuteIndex: Int? = null
|
||||
var defaultMinute = 0
|
||||
var hourIndex: Int? = null
|
||||
var defaultHour = 0
|
||||
while (i < array.length()) {
|
||||
when (array.getString(i)) {
|
||||
"com.android.launcher3.LEVEL_PER_TICK_ICON_ROUND" -> {
|
||||
i++
|
||||
drawable = array.getDrawable(i) as? LayerDrawable
|
||||
}
|
||||
val background = (drawable as? AdaptiveIconDrawable)?.background
|
||||
val foreground = (drawable as? AdaptiveIconDrawable)?.foreground ?: drawable
|
||||
|
||||
"com.android.launcher3.HOUR_LAYER_INDEX" -> {
|
||||
i++
|
||||
hourIndex = array.getInt(i, -1).takeIf { it != -1 }
|
||||
}
|
||||
if (foreground !is LayerDrawable) return null
|
||||
|
||||
"com.android.launcher3.MINUTE_LAYER_INDEX" -> {
|
||||
i++
|
||||
minuteIndex = array.getInt(i, -1).takeIf { it != -1 }
|
||||
}
|
||||
|
||||
"com.android.launcher3.DEFAULT_HOUR" -> {
|
||||
i++
|
||||
defaultHour = array.getInt(i, 0)
|
||||
}
|
||||
|
||||
"com.android.launcher3.DEFAULT_MINUTE" -> {
|
||||
i++
|
||||
defaultMinute = array.getInt(i, 0)
|
||||
}
|
||||
val layers = (0 until foreground.numberOfLayers).map {
|
||||
val drw = foreground.getDrawable(it)
|
||||
ClockSublayer(
|
||||
drawable = drw,
|
||||
role = when (it) {
|
||||
icon.config.hourLayer -> ClockSublayerRole.Hour
|
||||
icon.config.minuteLayer -> ClockSublayerRole.Minute
|
||||
icon.config.secondLayer -> ClockSublayerRole.Second
|
||||
else -> ClockSublayerRole.Static
|
||||
}
|
||||
i++
|
||||
}
|
||||
if (drawable != null && minuteIndex != null && hourIndex != null) {
|
||||
)
|
||||
}
|
||||
|
||||
return StaticLauncherIcon(
|
||||
return when {
|
||||
icon.themed && drawable is AdaptiveIconDrawable -> {
|
||||
StaticLauncherIcon(
|
||||
foregroundLayer = TintedClockLayer(
|
||||
sublayers = (0 until drawable.numberOfLayers).map {
|
||||
val drw = drawable.getDrawable(it)
|
||||
if (drw is RotateDrawable) {
|
||||
drw.level = when (it) {
|
||||
hourIndex -> {
|
||||
(12 - defaultHour) * 60
|
||||
}
|
||||
|
||||
minuteIndex -> {
|
||||
(60 - defaultMinute)
|
||||
}
|
||||
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
ClockSublayer(
|
||||
drawable = drw,
|
||||
role = when (it) {
|
||||
hourIndex -> ClockSublayerRole.Hour
|
||||
minuteIndex -> ClockSublayerRole.Minute
|
||||
else -> ClockSublayerRole.Static
|
||||
}
|
||||
)
|
||||
},
|
||||
defaultHour = icon.config.defaultHour,
|
||||
defaultMinute = icon.config.defaultMinute,
|
||||
defaultSecond = icon.config.defaultSecond,
|
||||
sublayers = layers,
|
||||
scale = 1.5f,
|
||||
),
|
||||
backgroundLayer = ColorLayer()
|
||||
backgroundLayer = ColorLayer(),
|
||||
)
|
||||
}
|
||||
icon.themed -> {
|
||||
StaticLauncherIcon(
|
||||
foregroundLayer = TintedClockLayer(
|
||||
defaultHour = icon.config.defaultHour,
|
||||
defaultMinute = icon.config.defaultMinute,
|
||||
defaultSecond = icon.config.defaultSecond,
|
||||
sublayers = layers,
|
||||
scale = 1f,
|
||||
),
|
||||
backgroundLayer = ColorLayer(),
|
||||
)
|
||||
}
|
||||
drawable is AdaptiveIconDrawable -> {
|
||||
StaticLauncherIcon(
|
||||
foregroundLayer = ClockLayer(
|
||||
defaultHour = icon.config.defaultHour,
|
||||
defaultMinute = icon.config.defaultMinute,
|
||||
defaultSecond = icon.config.defaultSecond,
|
||||
sublayers = layers,
|
||||
scale = 1.5f,
|
||||
),
|
||||
backgroundLayer = StaticIconLayer(
|
||||
icon = background!!,
|
||||
scale = 1.5f
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
StaticLauncherIcon(
|
||||
foregroundLayer = ClockLayer(
|
||||
defaultHour = icon.config.defaultHour,
|
||||
defaultMinute = icon.config.defaultMinute,
|
||||
defaultSecond = icon.config.defaultSecond,
|
||||
sublayers = layers,
|
||||
scale = 1f,
|
||||
),
|
||||
backgroundLayer = TransparentLayer,
|
||||
)
|
||||
}
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getThemedCalendarIcon(
|
||||
resources: Resources,
|
||||
resId: Int,
|
||||
iconProviderPackage: String
|
||||
): LauncherIcon? {
|
||||
try {
|
||||
val array = resources.obtainTypedArrayOrNull(resId) ?: return null
|
||||
if (array.length() != 31) return null
|
||||
|
||||
return ThemedDynamicCalendarIcon(
|
||||
resources = resources,
|
||||
resourceIds = IntArray(31) {
|
||||
array.getResourceId(it, 0).takeIf { it != 0 } ?: return null
|
||||
},
|
||||
)
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun searchIconPackIcon(query: String, iconPack: IconPack?): List<IconPackIcon> {
|
||||
suspend fun searchIconPackIcon(query: String, iconPack: IconPack?): List<IconPackAppIcon> {
|
||||
val iconDao = appDatabase.iconDao()
|
||||
val drawableQuery = query.replace(" ", "_").lowercase()
|
||||
return iconDao.searchIconPackIcons(
|
||||
@@ -435,17 +414,11 @@ class IconPackManager(
|
||||
componentQuery = "%$query%",
|
||||
nameQuery = "%$query%",
|
||||
iconPack = iconPack?.packageName,
|
||||
).map {
|
||||
IconPackIcon(it)
|
||||
).mapNotNull {
|
||||
IconPackAppIcon(it)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun searchThemedIcons(query: String): List<IconPackIcon> {
|
||||
val iconDao = appDatabase.iconDao()
|
||||
return iconDao.searchGreyscaleIcons("%$query%").map {
|
||||
IconPackIcon(it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.mm20.launcher2.icons
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
@@ -24,7 +25,6 @@ import de.mm20.launcher2.icons.providers.IconPackIconProvider
|
||||
import de.mm20.launcher2.icons.providers.IconProvider
|
||||
import de.mm20.launcher2.icons.providers.PlaceholderIconProvider
|
||||
import de.mm20.launcher2.icons.providers.SystemIconProvider
|
||||
import de.mm20.launcher2.icons.providers.ThemedIconProvider
|
||||
import de.mm20.launcher2.icons.providers.ThemedPlaceholderIconProvider
|
||||
import de.mm20.launcher2.icons.providers.getFirstIcon
|
||||
import de.mm20.launcher2.icons.transformations.ForceThemedIconTransformation
|
||||
@@ -39,11 +39,13 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.internal.ChannelFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -67,6 +69,11 @@ class IconRepository(
|
||||
private var iconProviders: MutableStateFlow<List<IconProvider>> = MutableStateFlow(listOf())
|
||||
private var placeholderProvider: IconProvider? = null
|
||||
|
||||
/**
|
||||
* Signal that installed icon packs have been updated. Force a reload of all icons.
|
||||
*/
|
||||
private val iconPacksUpdated = MutableSharedFlow<Unit>(1)
|
||||
|
||||
private var transformations: MutableStateFlow<List<LauncherIconTransformation>> =
|
||||
MutableStateFlow(
|
||||
listOf()
|
||||
@@ -83,51 +90,52 @@ class IconRepository(
|
||||
addDataScheme("package")
|
||||
})
|
||||
|
||||
iconPacksUpdated.tryEmit(Unit)
|
||||
|
||||
scope.launch {
|
||||
dataStore.data.map { it.icons }.distinctUntilChanged().collectLatest { settings ->
|
||||
val placeholderProvider = if (settings.themedIcons) {
|
||||
ThemedPlaceholderIconProvider(context)
|
||||
} else {
|
||||
PlaceholderIconProvider(context)
|
||||
}
|
||||
val providers = mutableListOf<IconProvider>()
|
||||
|
||||
if (settings.iconPack.isNotBlank()) {
|
||||
val pack = iconPackManager.getIconPack(settings.iconPack)
|
||||
if (pack != null) {
|
||||
providers.add(
|
||||
IconPackIconProvider(
|
||||
context,
|
||||
pack,
|
||||
iconPackManager
|
||||
)
|
||||
)
|
||||
iconPacksUpdated.collectLatest {
|
||||
val placeholderProvider = if (settings.themedIcons) {
|
||||
ThemedPlaceholderIconProvider(context)
|
||||
} else {
|
||||
Log.w("MM20", "Icon pack ${settings.iconPack} not found")
|
||||
PlaceholderIconProvider(context)
|
||||
}
|
||||
}
|
||||
if (settings.themedIcons) {
|
||||
providers.add(ThemedIconProvider(iconPackManager))
|
||||
}
|
||||
providers.add(DynamicClockIconProvider(context, settings.themedIcons))
|
||||
providers.add(CalendarIconProvider(context, settings.themedIcons))
|
||||
if (!isAtLeastApiLevel(33)) {
|
||||
providers.add(CompatIconProvider(context, settings.themedIcons))
|
||||
}
|
||||
providers.add(SystemIconProvider(context, settings.themedIcons))
|
||||
providers.add(placeholderProvider)
|
||||
cache.evictAll()
|
||||
val providers = mutableListOf<IconProvider>()
|
||||
|
||||
val transformations = mutableListOf<LauncherIconTransformation>()
|
||||
if (settings.iconPack.isNotBlank()) {
|
||||
val pack = iconPackManager.getIconPack(settings.iconPack)
|
||||
if (pack != null) {
|
||||
providers.add(
|
||||
IconPackIconProvider(
|
||||
context,
|
||||
pack,
|
||||
iconPackManager
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Log.w("MM20", "Icon pack ${settings.iconPack} not found")
|
||||
}
|
||||
}
|
||||
providers.add(DynamicClockIconProvider(context, settings.themedIcons))
|
||||
providers.add(CalendarIconProvider(context, settings.themedIcons))
|
||||
if (!isAtLeastApiLevel(33)) {
|
||||
providers.add(CompatIconProvider(context, settings.themedIcons))
|
||||
}
|
||||
providers.add(SystemIconProvider(context, settings.themedIcons))
|
||||
providers.add(placeholderProvider)
|
||||
cache.evictAll()
|
||||
|
||||
if (settings.adaptify) transformations.add(LegacyToAdaptiveTransformation())
|
||||
if (settings.themedIcons && settings.forceThemed) transformations.add(
|
||||
ForceThemedIconTransformation()
|
||||
)
|
||||
val transformations = mutableListOf<LauncherIconTransformation>()
|
||||
|
||||
this@IconRepository.placeholderProvider = placeholderProvider
|
||||
iconProviders.value = providers
|
||||
this@IconRepository.transformations.value = transformations
|
||||
if (settings.adaptify) transformations.add(LegacyToAdaptiveTransformation())
|
||||
if (settings.themedIcons && settings.forceThemed) transformations.add(
|
||||
ForceThemedIconTransformation()
|
||||
)
|
||||
|
||||
this@IconRepository.placeholderProvider = placeholderProvider
|
||||
iconProviders.value = providers
|
||||
this@IconRepository.transformations.value = transformations
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -215,7 +223,9 @@ class IconRepository(
|
||||
|
||||
fun requestIconPackListUpdate() {
|
||||
scope.launch {
|
||||
iconPackManager.updateIconPacks()
|
||||
iconPackManager.updateIconPacks().also {
|
||||
if (it)iconPacksUpdated.tryEmit(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,24 +295,14 @@ class IconRepository(
|
||||
iconPackIcons.mapNotNull {
|
||||
CustomIconPackIcon(
|
||||
iconPackPackage = it.iconPack,
|
||||
iconComponentName = it.componentName?.flattenToString()
|
||||
?: return@mapNotNull null
|
||||
iconActivityName = it.activityName,
|
||||
iconPackageName = it.packageName,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
val themedIcon = iconPackManager.getGreyscaleIcon(searchable.`package`)
|
||||
if (themedIcon != null && themedIcon.componentName?.packageName != null) {
|
||||
providerOptions.add(
|
||||
CustomThemedIcon(
|
||||
iconPackageName = themedIcon.componentName.packageName,
|
||||
)
|
||||
)
|
||||
} else {
|
||||
transformationOptions.add(
|
||||
ForceThemedIcon
|
||||
)
|
||||
}
|
||||
transformationOptions.add(
|
||||
ForceThemedIcon
|
||||
)
|
||||
} else {
|
||||
transformationOptions.add(
|
||||
ForceThemedIcon
|
||||
@@ -359,31 +359,18 @@ class IconRepository(
|
||||
suspend fun searchCustomIcons(query: String, iconPack: IconPack?): List<CustomIconWithPreview> {
|
||||
val transformations = this.transformations.first()
|
||||
val iconPackIcons = iconPackManager.searchIconPackIcon(query, iconPack).mapNotNull {
|
||||
val componentName = it.componentName ?: return@mapNotNull null
|
||||
|
||||
CustomIconWithPreview(
|
||||
customIcon = CustomIconPackIcon(
|
||||
iconPackPackage = it.iconPack,
|
||||
iconComponentName = componentName.flattenToString(),
|
||||
iconActivityName = it.activityName,
|
||||
iconPackageName = it.packageName,
|
||||
),
|
||||
preview = iconPackManager.getIcon(it.iconPack, componentName)
|
||||
preview = iconPackManager.getIcon(it.iconPack, it.packageName, it.activityName)
|
||||
?.transform(transformations) ?: return@mapNotNull null
|
||||
)
|
||||
}
|
||||
|
||||
val themedIcons = iconPackManager.searchThemedIcons(query).mapNotNull {
|
||||
val componentName = it.componentName ?: return@mapNotNull null
|
||||
|
||||
CustomIconWithPreview(
|
||||
customIcon = CustomThemedIcon(
|
||||
iconPackageName = componentName.packageName,
|
||||
),
|
||||
preview = iconPackManager.getThemedIcon(componentName.packageName)
|
||||
?.transform(transformations) ?: return@mapNotNull null
|
||||
)
|
||||
}
|
||||
|
||||
return iconPackIcons + themedIcons
|
||||
return iconPackIcons
|
||||
}
|
||||
|
||||
fun setCustomIcon(searchable: SavableSearchable, icon: CustomIcon?) {
|
||||
|
||||
+6
-17
@@ -145,23 +145,6 @@ fun AdaptiveIconDrawableCompat.toLauncherIcon(
|
||||
if (clock != null && clockForeground != null) {
|
||||
val clockLayers = (0 until clockForeground.numberOfLayers).map {
|
||||
val drw = clockForeground.getDrawable(it)
|
||||
if (drw is RotateDrawable) {
|
||||
drw.level = when (it) {
|
||||
clock.hourLayer -> {
|
||||
(12 - clock.defaultHour) * 60
|
||||
}
|
||||
|
||||
clock.minuteLayer -> {
|
||||
(60 - clock.defaultMinute)
|
||||
}
|
||||
|
||||
clock.secondLayer -> {
|
||||
(60 - clock.defaultSecond) * 10
|
||||
}
|
||||
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
ClockSublayer(
|
||||
drawable = drw,
|
||||
role = when (it) {
|
||||
@@ -175,6 +158,9 @@ fun AdaptiveIconDrawableCompat.toLauncherIcon(
|
||||
if (themed) {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = TintedClockLayer(
|
||||
defaultHour = clock.defaultHour,
|
||||
defaultMinute = clock.defaultMinute,
|
||||
defaultSecond = clock.defaultSecond,
|
||||
sublayers = clockLayers,
|
||||
scale = 1.5f,
|
||||
),
|
||||
@@ -183,6 +169,9 @@ fun AdaptiveIconDrawableCompat.toLauncherIcon(
|
||||
}
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = ClockLayer(
|
||||
defaultHour = clock.defaultHour,
|
||||
defaultMinute = clock.defaultMinute,
|
||||
defaultSecond = clock.defaultSecond,
|
||||
sublayers = clockLayers,
|
||||
scale = 1.5f,
|
||||
),
|
||||
|
||||
+197
@@ -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 }
|
||||
}
|
||||
}
|
||||
+178
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
-214
@@ -1,233 +1,53 @@
|
||||
package de.mm20.launcher2.icons.loaders
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.XmlResourceParser
|
||||
import android.util.Log
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import androidx.room.withTransaction
|
||||
import de.mm20.launcher2.database.AppDatabase
|
||||
import de.mm20.launcher2.icons.BuildConfig
|
||||
import de.mm20.launcher2.icons.IconPack
|
||||
import de.mm20.launcher2.icons.IconPackIcon
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
import org.xmlpull.v1.XmlPullParserFactory
|
||||
import java.io.IOException
|
||||
import java.io.Reader
|
||||
import de.mm20.launcher2.icons.IconPackComponent
|
||||
|
||||
class IconPackInstaller(
|
||||
private val context: Context,
|
||||
abstract class IconPackInstaller(
|
||||
private val database: AppDatabase,
|
||||
) {
|
||||
|
||||
fun installIcons() {
|
||||
val packs = loadInstalledPacks(context)
|
||||
val iconDao = database.iconDao()
|
||||
|
||||
for (pack in packs) {
|
||||
try {
|
||||
installIconPack(pack)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
iconDao.uninstallIconPacksExcept(
|
||||
packs.map { it.packageName }.toList()
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadInstalledPacks(context: Context): List<IconPack> {
|
||||
val packs = mutableListOf<IconPack>()
|
||||
val pm = context.packageManager
|
||||
var intent = Intent("app.lawnchair.icons.THEMED_ICON")
|
||||
val themedPacks = pm.queryIntentActivities(intent, 0)
|
||||
packs.addAll(themedPacks.map { IconPack(context, it, true) })
|
||||
intent = Intent("org.adw.ActivityStarter.THEMES")
|
||||
val adwPacks = pm.queryIntentActivities(intent, 0)
|
||||
packs.addAll(adwPacks.map { IconPack(context, it, false) })
|
||||
intent = Intent("com.novalauncher.THEME")
|
||||
val novaPacks = pm.queryIntentActivities(intent, 0)
|
||||
packs.addAll(novaPacks.map { IconPack(context, it, false) })
|
||||
return packs.distinctBy { it.packageName }
|
||||
}
|
||||
|
||||
private fun installIconPack(iconPack: IconPack) {
|
||||
val pkgName = iconPack.packageName
|
||||
|
||||
val icons = mutableListOf<IconPackIcon>()
|
||||
database.runInTransaction {
|
||||
try {
|
||||
val res = context.packageManager.getResourcesForApplication(pkgName)
|
||||
val parser: XmlPullParser
|
||||
var inStream: Reader? = null
|
||||
val xmlId = res.getIdentifier("appfilter", "xml", pkgName)
|
||||
val rawId = res.getIdentifier("appfilter", "raw", pkgName)
|
||||
parser = when {
|
||||
xmlId != 0 -> res.getXml(xmlId)
|
||||
rawId != 0 -> {
|
||||
inStream = res.openRawResource(rawId).reader()
|
||||
XmlPullParserFactory.newInstance().newPullParser().apply {
|
||||
setInput(inStream)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
val iconPackContext = context.createPackageContext(
|
||||
pkgName,
|
||||
Context.CONTEXT_IGNORE_SECURITY
|
||||
)
|
||||
inStream = try {
|
||||
iconPackContext.assets.open("appfilter.xml").reader()
|
||||
} catch (e: IOException) {
|
||||
CrashReporter.logException(e)
|
||||
Log.e(
|
||||
"MM20",
|
||||
"appfilter.xml not found in $pkgName. Searched locations: res/xml/appfilter.xml, res/raw/appfilter.xml, assets/appfilter.xml"
|
||||
)
|
||||
return@runInTransaction
|
||||
}
|
||||
XmlPullParserFactory.newInstance().newPullParser().apply {
|
||||
setInput(inStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
val iconDao = database.iconDao()
|
||||
|
||||
iconDao.deleteIconPack(iconPack.toDatabaseEntity())
|
||||
iconDao.deleteIcons(iconPack.packageName)
|
||||
|
||||
loop@ while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG) continue
|
||||
when (parser.name) {
|
||||
"item" -> {
|
||||
val component = parser.getAttributeValue(null, "component")
|
||||
?: continue@loop
|
||||
val drawable = parser.getAttributeValue(null, "drawable")
|
||||
?: continue@loop
|
||||
if (component.length <= 14) continue@loop
|
||||
val componentName = ComponentName.unflattenFromString(
|
||||
component.substring(
|
||||
14,
|
||||
component.lastIndex
|
||||
)
|
||||
)
|
||||
?: continue@loop
|
||||
|
||||
val name = parser.getAttributeValue(null, "name")
|
||||
|
||||
val icon = IconPackIcon(
|
||||
componentName = componentName,
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
name = name,
|
||||
themed = iconPack.themed,
|
||||
type = "app"
|
||||
)
|
||||
icons.add(icon)
|
||||
}
|
||||
|
||||
"calendar" -> {
|
||||
val component = parser.getAttributeValue(null, "component")
|
||||
?: continue@loop
|
||||
val drawable = parser.getAttributeValue(null, "prefix") ?: continue@loop
|
||||
if (component.length < 14) continue@loop
|
||||
val componentName = ComponentName.unflattenFromString(
|
||||
component.substring(
|
||||
14,
|
||||
component.lastIndex
|
||||
)
|
||||
)
|
||||
?: continue@loop
|
||||
|
||||
val name = parser.getAttributeValue(null, "name")
|
||||
|
||||
val icon = IconPackIcon(
|
||||
componentName = componentName,
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
type = "calendar",
|
||||
themed = iconPack.themed,
|
||||
name = name,
|
||||
)
|
||||
icons.add(icon)
|
||||
}
|
||||
|
||||
"iconback" -> {
|
||||
for (i in 0 until parser.attributeCount) {
|
||||
if (parser.getAttributeName(i).startsWith("img")) {
|
||||
val drawable = parser.getAttributeValue(i)
|
||||
val icon = IconPackIcon(
|
||||
componentName = null,
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
type = "iconback"
|
||||
)
|
||||
icons.add(icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"iconupon" -> {
|
||||
for (i in 0 until parser.attributeCount) {
|
||||
if (parser.getAttributeName(i).startsWith("img")) {
|
||||
val drawable = parser.getAttributeValue(i)
|
||||
val icon = IconPackIcon(
|
||||
componentName = null,
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
type = "iconupon"
|
||||
)
|
||||
icons.add(icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"iconmask" -> {
|
||||
for (i in 0 until parser.attributeCount) {
|
||||
if (parser.getAttributeName(i).startsWith("img")) {
|
||||
val drawable = parser.getAttributeValue(i)
|
||||
val icon = IconPackIcon(
|
||||
componentName = null,
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
type = "iconmask"
|
||||
)
|
||||
icons.add(icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"scale" -> {
|
||||
val scale = parser.getAttributeValue(null, "factor")?.toFloatOrNull()
|
||||
?: continue@loop
|
||||
iconPack.scale = scale
|
||||
}
|
||||
}
|
||||
suspend fun install(iconPack: IconPack) {
|
||||
var pack = iconPack
|
||||
val dao = database.iconDao()
|
||||
database.withTransaction {
|
||||
dao.deleteIconPack(iconPack.toDatabaseEntity())
|
||||
dao.deleteIcons(iconPack.packageName)
|
||||
val icons = mutableListOf<IconPackComponent>()
|
||||
val installerScope = object: IconPackInstallerScope {
|
||||
override suspend fun addIcon(icon: IconPackComponent) {
|
||||
icons.add(icon)
|
||||
if (icons.size >= 100) {
|
||||
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||
dao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||
icons.clear()
|
||||
}
|
||||
}
|
||||
|
||||
if (icons.isNotEmpty()) {
|
||||
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||
override suspend fun updatePackInfo(update: (IconPack) -> IconPack) {
|
||||
pack = update(iconPack)
|
||||
}
|
||||
iconDao.installIconPack(iconPack.toDatabaseEntity())
|
||||
|
||||
(parser as? XmlResourceParser)?.close()
|
||||
inStream?.close()
|
||||
|
||||
Log.d("MM20", "Icon pack has been installed successfully")
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Log.e("MM20", "Could not install icon pack $pkgName: package not found.")
|
||||
} catch (e: XmlPullParserException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
|
||||
installerScope.buildIconPack(iconPack)
|
||||
if (icons.isNotEmpty()) dao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||
dao.installIconPack(pack.toDatabaseEntity())
|
||||
}
|
||||
}
|
||||
|
||||
abstract suspend fun IconPackInstallerScope.buildIconPack(iconPack: IconPack)
|
||||
|
||||
abstract fun getInstalledIconPacks(): List<IconPack>
|
||||
|
||||
suspend fun isInstalledAndUpToDate(iconPack: IconPack): Boolean {
|
||||
if (BuildConfig.DEBUG) return false
|
||||
val dao = database.iconDao()
|
||||
val installed = dao.getIconPack(iconPack.packageName)?.let { IconPack(it) } ?: return false
|
||||
return installed.version == iconPack.version
|
||||
}
|
||||
}
|
||||
|
||||
interface IconPackInstallerScope {
|
||||
suspend fun addIcon(icon: IconPackComponent)
|
||||
suspend fun updatePackInfo(update: (IconPack) -> IconPack)
|
||||
}
|
||||
+2
-1
@@ -13,7 +13,8 @@ class CustomIconPackIconProvider(
|
||||
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
|
||||
return iconPackManager.getIcon(
|
||||
customIcon.iconPackPackage,
|
||||
ComponentName.unflattenFromString(customIcon.iconComponentName) ?: return null
|
||||
customIcon.iconPackageName,
|
||||
customIcon.iconActivityName,
|
||||
)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -10,6 +10,6 @@ class CustomThemedIconProvider(
|
||||
private val iconPackManager: IconPackManager,
|
||||
): IconProvider {
|
||||
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
|
||||
return iconPackManager.getThemedIcon(customIcon.iconPackageName)
|
||||
return null //iconPackManager.getThemedIcon(customIcon.iconPackageName)
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -16,8 +16,7 @@ class IconPackIconProvider(
|
||||
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
|
||||
if (searchable !is LauncherApp) return null
|
||||
|
||||
val component = ComponentName(searchable.`package`, searchable.activity)
|
||||
return iconPackManager.getIcon(iconPack.packageName, component)
|
||||
return iconPackManager.getIcon(iconPack.packageName, searchable.`package`, searchable.activity)
|
||||
?: iconPackManager.generateIcon(
|
||||
context,
|
||||
iconPack.packageName,
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package de.mm20.launcher2.icons.providers
|
||||
|
||||
import de.mm20.launcher2.icons.*
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.data.LauncherApp
|
||||
|
||||
internal class ThemedIconProvider(
|
||||
private val iconPackManager: IconPackManager,
|
||||
) : IconProvider {
|
||||
|
||||
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
|
||||
if (searchable !is LauncherApp) return null
|
||||
return iconPackManager.getThemedIcon(searchable.`package`)
|
||||
}
|
||||
}
|
||||
+3
@@ -22,6 +22,9 @@ internal class ThemedPlaceholderIconProvider(
|
||||
is ClockLayer -> TintedClockLayer(
|
||||
scale = layer.scale,
|
||||
color = 0,
|
||||
defaultHour = layer.defaultHour,
|
||||
defaultMinute = layer.defaultMinute,
|
||||
defaultSecond = layer.defaultSecond,
|
||||
sublayers = layer.sublayers,
|
||||
)
|
||||
is ColorLayer -> layer.copy(color = 0)
|
||||
|
||||
+3
@@ -14,6 +14,9 @@ internal class ForceThemedIconTransformation : LauncherIconTransformation {
|
||||
return when(layer) {
|
||||
is ClockLayer -> TintedClockLayer(
|
||||
scale = layer.scale,
|
||||
defaultHour = layer.defaultHour,
|
||||
defaultMinute = layer.defaultMinute,
|
||||
defaultSecond = layer.defaultSecond,
|
||||
sublayers = layer.sublayers,
|
||||
)
|
||||
is ColorLayer -> layer.copy(color = 0)
|
||||
|
||||
Reference in New Issue
Block a user